Previous Next

Zend_Form の高度な使用法

Zend_Form にはさまざまな機能があり、 その多くは熟練者向けに用意されています。本章では、 それらの機能について例を交えて説明します。

配列記法

関連するフォーム要素について、要素名を配列形式にしてグループ化したいこともあるでしょう。 たとえば、配送先と請求先のふたつの住所を受け取りたい場合、 それぞれに同じ要素を使った上で配列でグループ化すれば、 結果を別々に受け取ることができます。 たとえば次のようなフォームを例に考えてみましょう。

配送先
請求先

この例では、請求先住所と配送先住所に同じフィールドを使用しているため、 一方が他方を上書きしてしまいます。 これを解決するには、配列記法を使用します。

配送先
請求先

上の例では、住所をそれぞれ個別に受け取ることができます。 このフォームを送信すると、受け取り側では 3 つの要素を取得することができます。 'save' が送信ボタン、そしてふたつの配列 'shipping' と 'billing' の中にはさまざまなキーとそれに対応する要素が含まれています。

Zend_Form は、この処理を サブフォーム で自動化します。デフォルトで、 サブフォームのレンダリングには先ほどのような配列記法を使用します。 配列の名前はサブフォーム名からとられ、 配列のキーはサブフォーム内に含まれる要素となります。 サブフォームは、何段階でもネストさせることができます。 その場合も、ネストした配列形式でその構造を表します。 さらに、Zend_Form のさまざまなバリデーション機能は、この配列構造をきちんと処理するようにできています。 サブフォームをどれだけ深くネストさせたとしても、 フォームの検証は正しく行ってくれます。 この機能を使うために特に何かしなければならないということはありません。 この機能はデフォルトで有効になっています。

さらに、条件付きで配列記法を有効にしたり 特定の配列を指定してそこに要素やコレクションを所属させたりといった機能もあります。

  • Zend_Form::setIsArray($flag): このフラグを true にすると、フォーム全体を配列として扱うことができます。 デフォルトでは、setElementsBelongTo() がコールされていない限りはフォーム名を配列の名前とします。 フォームに名前が設定されていない場合や setElementsBelongTo() が設定されていない場合は、 このフラグは無視されます (要素が属する配列の名前がないからです)。

    フォームが配列として扱われているかどうかを知りたい場合には isArray() アクセサを使用します。

  • Zend_Form::setElementsBelongTo($array): このメソッドを使用すると、フォームの全要素が属する 配列の名前を指定することができます。現在設定されている値を調べるには getElementsBelongTo() アクセサを使用します。

さらに、要素レベルでは、特定の要素を特定の配列に属させるために Zend_Form_Element::setBelongsTo() メソッドを使うこともできます。 この値が何者なのか (明示的に設定されたものなのか フォームを経由して暗黙的に設定されたものなのか) を知るには getBelongsTo() アクセサを使用します。

複数ページのフォーム

現在、複数ページのフォームは Zend_Form では公式にはサポートしていません。 しかし、それを実装するための機能の大半はサポートしており、 ほんの少し手を加えるだけでこの機能を実現することができます。

複数ページのフォームを作成する鍵となるのが、 サブフォームの活用です。各ページに、ひとつのサブフォームだけを表示させるわけです。 こうすれば、それぞれのサブフォームの内容を各ページで検証し、 かつすべてのサブフォームの入力を終えるまでフォームの処理を行わないということができます。

Example #1 登録フォームの例

例として、登録フォームを考えてみましょう。 まず最初のページでユーザ名とパスワードを入力してもらい、 次のページではユーザのメタデータ (姓、名、住所など)、そして最後のページでは 参加したいメーリングリストを選択するといったものです。

まずはフォームを作成し、 その中でサブフォームをいくつか定義します。

class My_Form_Registration extends Zend_Form
{
    public function init()
    {
        // ユーザサブフォーム (ユーザ名とパスワード) を作成します
        $user = new Zend_Form_SubForm();
        $user->addElements(array(
            new Zend_Form_Element_Text('username', array(
                'required'   => true,
                'label'      => 'Username:',
                'filters'    => array('StringTrim', 'StringToLower'),
                'validators' => array(
                    'Alnum',
                    array('Regex',
                          false,
                          array('/^[a-z][a-z0-9]{2,}$/'))
                )
            )),

            new Zend_Form_Element_Password('password', array(
                'required'   => true,
                'label'      => 'Password:',
                'filters'    => array('StringTrim'),
                'validators' => array(
                    'NotEmpty',
                    array('StringLength', false, array(6))
                )
            )),
        ));

        // 詳細サブフォーム (姓、名、住所) を作成します
        $demog = new Zend_Form_SubForm();
        $demog->addElements(array(
            new Zend_Form_Element_Text('givenName', array(
                'required'   => true,
                'label'      => 'Given (First) Name:',
                'filters'    => array('StringTrim'),
                'validators' => array(
                    array('Regex',
                          false,
                          array('/^[a-z][a-z0-9., \'-]{2,}$/i'))
                )
            )),

            new Zend_Form_Element_Text('familyName', array(
                'required'   => true,
                'label'      => 'Family (Last) Name:',
                'filters'    => array('StringTrim'),
                'validators' => array(
                    array('Regex',
                          false,
                          array('/^[a-z][a-z0-9., \'-]{2,}$/i'))
                )
            )),

            new Zend_Form_Element_Text('location', array(
                'required'   => true,
                'label'      => 'Your Location:',
                'filters'    => array('StringTrim'),
                'validators' => array(
                    array('StringLength', false, array(2))
                )
            )),
        ));

        // メーリングリストサブフォームを作成します
        $listOptions = array(
            'none'        => 'No lists, please',
            'fw-general'  => 'Zend Framework General List',
            'fw-mvc'      => 'Zend Framework MVC List',
            'fw-auth'     => 'Zend Framwork Authentication and ACL List',
            'fw-services' => 'Zend Framework Web Services List',
        );
        $lists = new Zend_Form_SubForm();
        $lists->addElements(array(
            new Zend_Form_Element_MultiCheckbox('subscriptions', array(
                'label'        =>
                    'Which lists would you like to subscribe to?',
                'multiOptions' => $listOptions,
                'required'     => true,
                'filters'      => array('StringTrim'),
                'validators'   => array(
                    array('InArray',
                          false,
                          array(array_keys($listOptions)))
                )
            )),
        ));

        // サブフォームをメインフォームにアタッチします
        $this->addSubForms(array(
            'user'  => $user,
            'demog' => $demog,
            'lists' => $lists
        ));
    }
}

submit ボタンがないこと、 またサブフォームのデコレータではなにもしていないことに注意しましょう。 そのままでは、これらのサブフォームはフィールドセットとして表示されることになります。 つまり、処理をオーバーライドしてそれらを個別のサブフォームになるようにし、 さらに submit ボタンを追加して処理を進められるようにする必要があります。 submit ボタンには action プロパティと method プロパティも必要です。 では、これらの機能のとっかかりを先ほどのクラスに追加してみましょう。

class My_Form_Registration extends Zend_Form
{
    // ...

    /**
     * 表示用のサブフォームを準備する
     *
     * @param  string|Zend_Form_SubForm $spec
     * @return Zend_Form_SubForm
     */
    public function prepareSubForm($spec)
    {
        if (is_string($spec)) {
            $subForm = $this->{$spec};
        } elseif ($spec instanceof Zend_Form_SubForm) {
            $subForm = $spec;
        } else {
            throw new Exception('Invalid argument passed to ' .
                                __FUNCTION__ . '()');
        }
        $this->setSubFormDecorators($subForm)
             ->addSubmitButton($subForm)
             ->addSubFormActions($subForm);
        return $subForm;
    }

    /**
     * Form デコレータを各サブフォームに追加する
     *
     * @param  Zend_Form_SubForm $subForm
     * @return My_Form_Registration
     */
    public function setSubFormDecorators(Zend_Form_SubForm $subForm)
    {
        $subForm->setDecorators(array(
            'FormElements',
            array('HtmlTag', array('tag' => 'dl',
                                   'class' => 'zend_form')),
            'Form',
        ));
        return $this;
    }

    /**
     * submit ボタンを各サブフォームに追加する
     *
     * @param  Zend_Form_SubForm $subForm
     * @return My_Form_Registration
     */
    public function addSubmitButton(Zend_Form_SubForm $subForm)
    {
        $subForm->addElement(new Zend_Form_Element_Submit(
            'save',
            array(
                'label'    => 'Save and continue',
                'required' => false,
                'ignore'   => true,
            )
        ));
        return $this;
    }

    /**
     * action と method をサブフォームに追加する
     *
     * @param  Zend_Form_SubForm $subForm
     * @return My_Form_Registration
     */
    public function addSubFormActions(Zend_Form_SubForm $subForm)
    {
        $subForm->setAction('/registration/process')
                ->setMethod('post');
        return $this;
    }
}

次に、アクションコントローラ用の仕組みを追加する必要があります。 さらにいくつか考えなければならないこともあります。 まず、フォームの入力内容をリクエスト間で持続させなければなりません。 次に、フォームの情報のうちどの部分が入力済みなのか、 そしてその部分に対応するサブフォームがどれなのか といった情報を取得するロジックも必要です。今回は Zend_Session_Namespace を使用してデータを持続させることにします。 そうすれば、二番目の問題に対応するのも簡単になるでしょう。

それではコントローラを作成していきましょう。 そして、フォームのインスタンスを取得するためのメソッドを追加します。

class RegistrationController extends Zend_Controller_Action
{
    protected $_form;

    public function getForm()
    {
        if (null === $this->_form) {
            $this->_form = new My_Form_Registration();
        }
        return $this->_form;
    }
}

それでは、どのフォームを表示するのかを決める機能を追加していきましょう。 基本的に、フォーム全体の入力内容の検証を終えるまでは フォームの一部の表示を続けることになります。 さらに、普通はそれを決まった順序で表示することになるでしょう。 今回の場合は user、demog、そして最後に lists といった具合です。 どのデータが入力済みかを調べるには、セッションの名前空間を調べます。 各サブフォームに対応するキーが存在するかどうかを調べるというわけです。

class RegistrationController extends Zend_Controller_Action
{
    // ...

    protected $_namespace = 'RegistrationController';
    protected $_session;

    /**
     * 使用するセッション名前空間を取得する
     *
     * @return Zend_Session_Namespace
     */
    public function getSessionNamespace()
    {
        if (null === $this->_session) {
            $this->_session =
                new Zend_Session_Namespace($this->_namespace);
        }

        return $this->_session;
    }

    /**
     * すでにセッションに保存済みであるフォームの一覧を取得する
     *
     * @return array
     */
    public function getStoredForms()
    {
        $stored = array();
        foreach ($this->getSessionNamespace() as $key => $value) {
            $stored[] = $key;
        }

        return $stored;
    }

    /**
     * 使用できるすべてのサブフォームの一覧を取得する
     *
     * @return array
     */
    public function getPotentialForms()
    {
        return array_keys($this->getForm()->getSubForms());
    }

    /**
     * 今どのサブフォームが送信されたのか?
     *
     * @return false|Zend_Form_SubForm
     */
    public function getCurrentSubForm()
    {
        $request = $this->getRequest();
        if (!$request->isPost()) {
            return false;
        }

        foreach ($this->getPotentialForms() as $name) {
            if ($data = $request->getPost($name, false)) {
                if (is_array($data)) {
                    return $this->getForm()->getSubForm($name);
                    break;
                }
            }
        }

        return false;
    }

    /**
     * 次に表示するサブフォームを取得する
     *
     * @return Zend_Form_SubForm|false
     */
    public function getNextSubForm()
    {
        $storedForms    = $this->getStoredForms();
        $potentialForms = $this->getPotentialForms();

        foreach ($potentialForms as $name) {
            if (!in_array($name, $storedForms)) {
                return $this->getForm()->getSubForm($name);
            }
        }

        return false;
    }
}

上のメソッドを使用すると、たとえば "$subForm = $this->getCurrentSubForm();" で現在のサブフォームを取得してそれを検証したり "$next = $this->getNextSubForm();" で次に表示するフォームを取得したりすることができます。

では、実際にサブフォームを処理したり表示したりする方法を考えてみましょう。 getCurrentSubForm() を使用すれば、 今送信されてきたデータがどのサブフォームのものなのかがわかります (false が返された場合は、まだ何も表示あるいは送信されていないことを表します)。 また、getNextSubForm() を使用すれば次に表示すべきフォームを取得することができます。 そして、フォームの prepareSubForm() メソッドを使用すれば、フォームを表示するための準備をすることができます。

フォームを送信したら、サブフォームのデータを検証し、 そしてフォーム全体の入力が完了したかどうかを調べることができます。 これらの作業を行うためには、さらにいくつかのメソッドを追加しなければなりません。 送信されたデータをセッションに追加するメソッドや、 フォーム全体の検証を行う際にセッションの全セグメントを検証するメソッドなどです。

class RegistrationController extends Zend_Controller_Action
{
    // ...

    /**
     * サブフォームの入力は妥当か?
     *
     * @param  Zend_Form_SubForm $subForm
     * @param  array $data
     * @return bool
     */
    public function subFormIsValid(Zend_Form_SubForm $subForm,
                                   array $data)
    {
        $name = $subForm->getName();
        if ($subForm->isValid($data)) {
            $this->getSessionNamespace()->$name = $subForm->getValues();
            return true;
        }

        return false;
    }

    /**
     * フォーム全体の入力は妥当か?
     *
     * @return bool
     */
    public function formIsValid()
    {
        $data = array();
        foreach ($this->getSessionNamespace() as $key => $info) {
            $data[$key] = $info;
        }

        return $this->getForm()->isValid($data);
    }
}

これで足場は固まりました。 ではこのコントローラのアクションを作っていきましょう。 まずこのフォームの最初のページ、 それからフォームを処理するための 'process' アクションが必要となります。

class RegistrationController extends Zend_Controller_Action
{
    // ...

    public function indexAction()
    {
        // 現在のページを再表示するか、"次の" (最初の)
        // サブフォームを取得します
        if (!$form = $this->getCurrentSubForm()) {
            $form = $this->getNextSubForm();
        }
        $this->view->form = $this->getForm()->prepareSubForm($form);
    }

    public function processAction()
    {
        if (!$form = $this->getCurrentSubForm()) {
            return $this->_forward('index');
        }

        if (!$this->subFormIsValid($form,
                                   $this->getRequest()->getPost())) {
            $this->view->form = $this->getForm()->prepareSubForm($form);
            return $this->render('index');
        }

        if (!$this->formIsValid()) {
            $form = $this->getNextSubForm();
            $this->view->form = $this->getForm()->prepareSubForm($form);
            return $this->render('index');
        }

        // フォームの入力が完了しました!
        // 確認ページに情報を表示します
        $this->view->info = $this->getSessionNamespace();
        $this->render('verification');
    }
}

お気づきのとおり、実際にフォームを処理する部分のコードは比較的シンプルです。 注目すべき点は、どのサブフォームが送信されてきたのかを調べ、 何も送信されていない場合は先頭ページに飛ばしている部分です。 サブフォームが送信されてきた場合はそれを検証し、 問題がある場合は同じサブフォームを再表示します。 問題がない場合は、フォーム全体の入力が妥当か (つまりすべての入力が終わっているか) を調べ、 問題がある場合は次のサブフォームを表示します。 最後に、セッションの中身を確認ページに表示します。

ビュースクリプトは非常にシンプルなものになります。


登録

form ?>

登録ありがとうございます!

入力された情報は次のとおりです。

info as $info): foreach ($info as $form => $data): ?>

:

$value): ?>
$val): ?>
escape($value) ?>

将来的に、Zend Framework には複数ページのフォームを より簡単に作成するためのコンポーネントが用意される予定です。 このコンポーネントは、 セッションや各フォームの順序などの管理を抽象化したものとなります。 現時点では、複数ページのフォームをあなたのサイトで使用するには 上の例のようにするのが最も無難でしょう。

Previous Next
Introduction to Zend Framework
概要
インストール
Zend_Acl
導入
アクセス制御の洗練
高度な使用法
Zend_Amf
導入
Zend_Amf_Server
Zend_Application
導入
Zend_Application Quick Start
Theory of Operation
Examples
コア機能
利用できるリソースプラグイン
Zend_Auth
導入
データベースのテーブルでの認証
ダイジェスト認証
HTTP 認証アダプタ
LDAP 認証
Open ID 認証
Zend_Cache
導入
キャッシュの仕組み
Zend_Cache のフロントエンド
Zend_Cache のバックエンド
Zend_Captcha
導入
Captcha の方法
CAPTCHA アダプタ
Zend_CodeGenerator
導入
Zend_CodeGeneratorサンプル
Zend_CodeGeneratorリファレンス
Zend_Config
導入
動作原理
Zend_Config_Ini
Zend_Config_Xml
Zend_Config_Writer
Zend_Config_Writer
Zend_Console_Getopt
導入
Getopt の規則の宣言
オプションおよび引数の取得
Zend_Console_Getopt の設定
Zend_Controller
Zend_Controller クイックスタート
Zend_Controller の基本
フロントコントローラ
リクエストオブジェクト
標準のルータ
ディスパッチャ
アクションコントローラ
アクションヘルパー
レスポンスオブジェクト
プラグイン
モジュラーディレクトリ構造の規約の使用
MVC での例外
以前のバージョンからの移行
Zend_Currency
Zend_Currency について
通貨の操作方法
以前のバージョンからの移行
Zend_Date
導入
動作原理
基本メソッド
Zend_Date API の概要
日付の作成
日付関数全般用の定数
動作例
Zend_Db
Zend_Db_Adapter
Zend_Db_Statement
Zend_Db_Profiler
Zend_Db_Select
Zend_Db_Table
Zend_Db_Table_Row
Zend_Db_Table_Rowset
導入
Zend_Debug
変数の出力
Zend_Dojo
導入
Zend_Dojo_Data: dojo.data エンベロープ
Dojo ビューヘルパー
Dojoフォーム要素とデコレーター
Zend_Dom
導入
Zend_Dom_Query
Zend_Exception
例外の使用法
Zend_Feed
導入
フィードの読み込み
ウェブページからのフィードの取得
RSS フィードの使用
Atom フィードの使用
単一の Atom エントリの処理
フィードおよびエントリの構造の変更
独自のフィードクラスおよびエントリクラス
Zend_File
Zend_File_Transfer
Zend_File_Transfer 用のバリデータ
Filters for Zend_File_Transfer
以前のバージョンからの移行
Zend_Filter
導入
標準のフィルタクラス群
フィルタチェイン
フィルタの書き方
Zend_Filter_Input
Zend_Filter_Inflector
Zend_Form
Zend_Form
Zend_Form クイックスタート
Zend_Form_Element を用いたフォーム要素の作成
Zend_Form によるフォームの作成
Zend_Form_Decorator による独自のフォームマークアップの作成
Zend Framework に同梱されている標準のフォーム要素
Zend Framework に同梱されている標準のデコレータ
Zend_Form の国際化
Zend_Form の高度な使用法
Zend_Gdata
導入
AuthSub による認証
Using the Book Search Data API
ClientLogin による認証
Google Calendar の使用法
Google Documents List Data API の使用法
Using Google Health
Google Spreadsheets の使用法
Google Apps Provisioning の使用法
Google Base の使用法
Picasa Web Albums の使用法
YouTube Data API の使用法
Gdata の例外処理
Zend_Http
導入
Zend_Http_Client - 高度な使用法
Zend_Http_Client - 接続アダプタ
Zend_Http_Cookie および Zend_Http_CookieJar
Zend_Http_Response
Zend_InfoCard
導入
Zend_Json
導入
基本的な使用法
Zend_Json の高度な使用法
XML から JSON への変換
Zend_Json_Server - JSON-RPCサーバー
Zend_Layout
導入
Zend_Layout クイックスタート
Zend_Layout の設定オプション
Zend_Layout の高度な使用法
Zend_Ldap
導入
Zend_Loader
ファイルやクラスの動的な読み込み
The Autoloader
Resource Autoloaders
プラグインのロード
Zend_Locale
導入
Zend_Locale の使用法
正規化および地域化
日付および時刻の扱い
サポートするロケール
以前のバージョンからの移行
Zend_Log
概要
ライター
フォーマッタ
フィルタ
Zend_Mail
導入
SMTP 経由での送信
SMTP 接続による複数のメールの送信
異なる転送手段の使用
HTML メール
ファイルの添付
受信者の追加
MIME バウンダリの制御
追加のヘッダ
文字セット
エンコーディング
SMTP 認証
セキュアな SMTP トランスポート
メールメッセージの読み込み
Zend_Measure
導入
計測値の作成
計測値の出力
計測値の操作
計測値の型
Zend_Memory
概要
メモリマネージャ
メモリオブジェクト
Zend_Mime
Zend_Mime
Zend_Mime_Message
Zend_Mime_Part
Zend_Navigation
Introduction
画面
Containers
Zend_OpenId
導入
Zend_OpenId_Consumer の基本
Zend_OpenId_Provider
Zend_Paginator
導入
使用法
設定
高度な使用法
Zend_Pdf
導入
PDF ドキュメントの作成および読み込み
PDF ドキュメントへの変更内容の保存
ページの操作
描画
ドキュメントの情報およびメタデータ
Zend_Pdf モジュールの使用例
Zend_ProgressBar
Zend_ProgressBar
Zend_Reflection
導入
Zend_Reflectionサンプル
Zend_Reflectionリファレンス
Zend_Registry
レジストリの使用法
Zend_Rest
導入
Zend_Rest_Client
Zend_Rest_Server
Zend_Search_Lucene
概要
インデックスの構築
インデックスの検索
クエリ言語
クエリ作成用の API
文字セット
拡張性
Java Lucene との相互運用
応用
ベストプラクティス
Zend_Server
導入
Zend_Server_Reflection
Zend_Service
導入
Zend_Service_Akismet
Zend_Service_Amazon
Zend_Service_Amazon_Ec2
Zend_Service_Amazon_Ec2: Instances
Zend_Service_Amazon_Ec2: Windows Instances
Zend_Service_Amazon_Ec2: Reserved Instances
Zend_Service_Amazon_Ec2: CloudWatch Monitoring
Zend_Service_Amazon_Ec2: Amazon Machine Images (AMI)
Zend_Service_Amazon_Ec2: Elastic Block Stroage (EBS)
Zend_Service_Amazon_Ec2: Elastic IP Addresses
Zend_Service_Amazon_Ec2: Keypairs
Zend_Service_Amazon_Ec2: Regions and Availability Zones
Zend_Service_Amazon_Ec2: Security Groups
Zend_Service_Amazon_S3
Zend_Service_Audioscrobbler
Zend_Service_Delicious
Zend_Service_Flickr
Zend_Service_Nirvanix
Zend_Service_ReCaptcha
Zend_Service_Simpy
導入
Zend_Service_StrikeIron
Zend_Service_StrikeIron: バンドルされているサービス
Zend_Service_StrikeIron: 応用編
Zend_Service_Technorati
Zend_Service_Twitter
Zend_Service_Yahoo
Zend_Session
導入
基本的な使用法
高度な使用法
グローバルセッションの管理
Zend_Session_SaveHandler_DbTable
Zend_Soap
Zend_Soap_Server
Zend_Soap_Client
WSDLアクセッサ
自動検出
Zend_Tag
Introduction
Zend_Tag_Cloud
Zend_Test
導入
Zend_Test_PHPUnit
Zend_Text
Zend_Text_Figlet
Zend_Text_Table
Zend_TimeSync
導入
Zend_TimeSync の動作
Zend_Tool_Framework
Introduction
Using the CLI Tool
Architecture
Creating Providers to use with Zend_Tool_Framework
Shipped System Providers
Zend_Tool_Project
Zend_Tool_Project導入
Create A Project
Zend Tool Project Providers
Zend_Translate
導入
Zend_Translate のアダプタ
翻訳アダプタの使用法
以前のバージョンからの移行
Zend_Uri
Zend_Uri
Zend_Validate
導入
標準のバリデーションクラス群
バリデータチェイン
バリデータの書き方
検証メッセージ
Zend_Version
Zend Framework のバージョンの取得
Zend_View
導入
コントローラスクリプト
ビュースクリプト
ビューヘルパー
Zend_View_Abstract
以前のバージョンからの移行
Zend_Wildfire
Zend_Wildfire
Zend_XmlRpc
導入
Zend_XmlRpc_Client
Zend_XmlRpc_Server
Zend Framework のシステム要件
導入
Zend Framework PHP 標準コーディング規約
概要
PHP ファイルの書式
命名規約
コーディングスタイル
Zend Framework Performance Guide
導入
クラスの読み込み
Zend_Dbパフォーマンス
国際化(i18n)とローカライズ(l10n)
ビューのレンダリング
著作権に関する情報