# ビューヘルパー

ビュースクリプトの中で、複雑な関数を繰り返し実行しなければならないこともあるでしょう (例えば日付のフォーマット、フォーム要素の作成、リンクの作成など)。 このような作業を行うために、ヘルパークラスを使用できます。

ヘルパーの正体は、単なるクラスです。たとえば 'fooBar' という名前のヘルパーを使用するとしましょう。 デフォルトでは、クラス名の先頭には 'Zend_View_Helper_' がつきます (ヘルパーのパスを設定する際に、これは独自のものに変更できます)。 そしてクラス名の最後の部分がヘルパーの名前となります。 このとき、単語の先頭は大文字にしなければなりません。つまり、 ヘルパーのクラス名は Zend_View_Helper_FooBar となります。このクラスには、最低限ヘルパー名と同じ名前 (camelCase 形式にしたもの) のメソッド fooBar() が含まれていなければなりません。

大文字小文字の扱い

ヘルパー名は常に camelCase 形式となります。 つまり、最初は常に小文字となります。 クラス名は MixedCase 形式ですが、実際に実行されるメソッドは camelCase 形式となります。

デフォルトのヘルパーのパス

デフォルトのヘルパーのパスは常に Zend Framework のビューヘルパーのパス、すなわち 'Zend/View/Helper/' となります。 setHelperPath() をコールして既存のパスを上書きしても、 このパスだけは残ります。これにより、 デフォルトのヘルパーは常に動作することが保証されます。

ビュースクリプト内でヘルパーを使用するには、 `$this->helperName()` をコールします。これをコールすると、裏側では Zend_View が Zend_View_Helper_HelperName クラスを読み込み、 そのクラスのインスタンスを作成して helperName() メソッドをコールします。 オブジェクトのインスタンスは Zend_View インスタンスの中に残り続け、 後で `$this->helperName()` がコールされたときには再利用されます。

## 付属のヘルパー

Zend_View には、はじめからいくつかのヘルパークラスが付属しています。 これらのほとんどはフォーム要素の生成に関するもので、 適切なエスケープ処理を自動的に行います。 さらに、ルートにもとづいた URL と HTML の一覧を作成したり、 変数を宣言したりするものもあります。 現在付属しているヘルパーは、次のとおりです。

- declareVars(): strictVars() を使用する際に同時に使用します。 このヘルパーを使用すると、テンプレート変数を宣言できます。 この変数は、すでにビュースクリプトで設定されているものでもいないものでもかまいません。 また、同時にデフォルト値も設定します。 このメソッドへの引数として配列を渡すとデフォルト値を設定します。 それ以外の場合は、もしその変数が存在しない場合は空文字列を設定します。

- fieldset($name, $content, $attribs): XHTML の fieldset を作成します。`$attribs` に 'legend' というキーが含まれる場合、その値をフィールドセットの説明として使用します。 フィールドセットで囲む内容は、このヘルパーに渡した `$content` です。

- form($name, $attribs, $content): XHTML の form を作成します。すべての `$attribs` はエスケープされ、form タグの XHTML 属性としてレンダリングされます。 `$content` が存在してその値が `FALSE` 以外の場合は、 その内容がフォームの開始タグと終了タグの間にレンダリングされます。 `$content` が `FALSE` (既定値) の場合は、 フォームの開始タグのみを作成します。

- formButton($name, $value, $attribs): <button /> 要素を作成します。

- formCheckbox($name, $value, $attribs, $options): <input type="checkbox" /> 要素を作成します。

  デフォルトでは、$value を指定せず $options が存在しなかった場合は、未チェックの値として '0' そしてチェック状態の値として '1' を使用します。 $value が渡されたけれど $options が存在しなかった場合は、 渡された値をチェック状態の値とみなします。 チェックボックスをレンダリングする前に hidden 入力要素をレンダリングすることにより、 unchecked 値を実装します。

  $options は配列でなければなりません。 数値添字形式の配列の場合は、最初の要素がチェック状態の値となり、 その次の要素が未チェック状態の値となります。 3 番目以降の要素の内容は無視されます。 キー 'checked' および 'unChecked' を持つ連想配列を指定することもできます。 unchecked 値に対して hidden フィールドをレンダリングすることを防ぐには、 キー 'disableHidden' を true に設定できます。

  $options を渡した場合は、$value が「チェック状態の値」と一致した場合にその要素がチェック状態だとみなされます。 チェック状態あるいは未チェック状態は、 属性 'checked' に boolean 値を渡して指定することもできます。

  これらの内容を簡単にまとめた例を示します。

      // '1' および '0' でチェック状態と未チェック状態を表します。これはチェックされていません
      echo $this->formCheckbox('foo');

      // '1' および '0' でチェック状態と未チェック状態を表します。これはチェックされています
      echo $this->formCheckbox('foo', null, array('checked' => true));

      // 'bar' および '0' でチェック状態と未チェック状態を表します。これはチェックされていません
      echo $this->formCheckbox('foo', 'bar');

      // 'bar' および '0' でチェック状態と未チェック状態を表します。これはチェックされています
      echo $this->formCheckbox('foo', 'bar', array('checked' => true));

      // 'bar' および 'baz' でチェック状態と未チェック状態を表します。これはチェックされていません
      echo $this->formCheckbox('foo', null, null, array('bar', 'baz'));

      // 'bar' および 'baz' でチェック状態と未チェック状態を表します。これはチェックされていません
      echo $this->formCheckbox('foo', null, null, array(
          'checked' => 'bar',
          'unChecked' => 'baz'
      ));

      // 'bar' および 'baz' でチェック状態と未チェック状態を表します。これはチェックされています
      echo $this->formCheckbox('foo', 'bar', null, array('bar', 'baz'));
      echo $this->formCheckbox('foo',
                               null,
                               array('checked' => true),
                               array('bar', 'baz'));

      // 'bar' および 'baz' でチェック状態と未チェック状態を表します。これはチェックされていません
      echo $this->formCheckbox('foo', 'baz', null, array('bar', 'baz'));
      echo $this->formCheckbox('foo',
                               null,
                               array('checked' => false),
                               array('bar', 'baz'));

  どの場合についても、マークアップの先頭に hidden 要素を追加してそこに「未チェック状態」を表す値を保持します。 そうすることで、仮に未チェック状態であったとしても フォームから何らかの値が返されるようになるのです。

- formErrors($errors, $options): エラーの表示用に、XHTML の順序なしリストを作成します。 `$errors` は、文字列あるいは文字列の配列となります。 `$options` は、リストの開始タグの属性として設定したい内容です。

  エラーをレンダリングする際に、ヘルパーのいくつかのメソッドを呼び出して代わりの開始、終了および区切りの コンテンツを指定することもできます。

  - setElementStart($string); デフォルトは '<ul class="errors"%s"><li>' で、%s の部分には `$options` で指定した属性が入ります。

  - setElementSeparator($string); デフォルトは '</li><li>' です。

  - setElementEnd($string); デフォルトは '</li></ul>' です。

- formFile($name, $attribs): type="file" /> 要素を作成します。

- formHidden($name, $value, $attribs): <input type="hidden" /> 要素を作成します。

- formImage($name, $value, $attribs): <input type="image" /> 要素を作成します。

      echo $this->formImage(
          'foo',
          'bar',
          array(
              'src' => 'images/button.png',
              'alt' => 'Button',
          )
      );
      // 出力: <input type="image" name="foo" id="foo" src="images/button.png" value="bar" alt="Button" />

- formLabel($name, $value, $attribs): <label> 要素を作成します。for 属性の値は `$name` に、そしてラベルのテキストは `$value` になります。 attribs に disable を渡すと、結果を何も返しません。

- formMultiCheckbox($name, $value, $attribs, $options, $listsep): チェックボックスのリストを作成します。 `$options` は連想配列で、任意の深さにできます。 `$value` は単一の値か複数の値の配列で、これが `$options` 配列のキーにマッチします。 `$listsep` は、デフォルトでは HTML の改行 ("<br />") です。デフォルトでは、この要素は配列として扱われます。 つまり、すべてのチェックボックスは同じ名前となり、入力内容は配列形式で送信されます。

      // リスト・セパレーター ($listsep) を使用:

      echo '<ul><li>';
      echo $view->formMultiCheckbox(
          'foo',
          2,
          array(
              'class' => 'baz',
          ),
          array(
              1 => 'One',
              2 => 'Two',
              3 => 'Three',
          ),
          "</li>\n<li>"
      );
      echo '</li></ul>';

      /*
      出力:
      <ul>
          <li>
              <label for="foo-1">
                  <input type="checkbox" name="foo[]" id="foo-1" value="1" class="baz" />One
              </label>
          </li>
          <li>
              <label for="foo-2">
                  <input type="checkbox" name="foo[]" id="foo-2" value="2" checked="checked" class="baz" />Two
              </label>
          </li>
          <li>
              <label for="foo-3">
                  <input type="checkbox" name="foo[]" id="foo-3" value="3" class="baz" />Three</label>
              </li>
          </ul>
      */

      // ラベル要素に対してオプションを使用:
      echo $this->formMultiCheckbox(
          'foo',
          2,
          array(
              'label_placement' => 'prepend',
              'label_class'     => 'baz',
          ),
          array(
              1 => 'One',
              2 => 'Two',
              3 => 'Three',
          )
      );

      /*
      出力:
      <label class="baz" for="foo-1">
          One<input type="checkbox" name="foo[]" id="foo-1" value="1" />
      </label>
      <br />
      <label class="baz" for="foo-2">
          Two<input type="checkbox" name="foo[]" id="foo-2" value="2" checked="checked" />
      </label>
      <br />
      <label class="baz" for="foo-3">
          Three<input type="checkbox" name="foo[]" id="foo-3" value="3" />
      </label>
      */

- formNote($name, $value = null): 単純なテキスト・ノートを作成。例えば、 Zend_Form オブジェクトの見出しに対する要素として。

      echo $this->formNote(null, 'This is an example text.');
      // 出力: This is an example text.

- formPassword($name, $value, $attribs): <input type="password" /> 要素を作成します。

- formRadio($name, $value, $attribs, $options, $listsep): 一連の <input type="radio" /> 要素を、$options の要素ごとに作成します。 $options は、ラジオボタンの値をキー、ラベルを値とする配列となります。 $value はラジオボタンの初期選択状態を設定します。

      // リスト・セパレーター ($listsep) を使用

      echo '<ul><li>';
      echo $this->formRadio(
          'foo',
          2,
          array(
              'class' => 'baz',
          ),
          array(
              1 => 'One',
              2 => 'Two',
              3 => 'Three',
          ),
          "</li>\n<li>"
      );
      echo '</li></ul>';

      /*
      出力:
      <ul>
          <li>
              <label for="foo-1">
                  <input type="radio" name="foo" id="foo-1" value="1" class="baz" />One
              </label>
          </li>
          <li>
              <label for="foo-2">
                  <input type="radio" name="foo" id="foo-2" value="2" checked="checked" class="baz" />Two
              </label>
          </li>
          <li>
              <label for="foo-3">
                  <input type="radio" name="foo" id="foo-3" value="3" class="baz" />Three
              </label>
          </li>
      </ul>
      */

      // ラベル要素に対してオプションを使用:

      echo $this->formRadio(
          'foo',
          2,
          array(
              'label_placement' => 'prepend',
              'label_class'     => 'baz',
          ),
          array(
              1 => 'One',
              2 => 'Two',
              3 => 'Three',
          )
      );

      /*
      出力:
      <label class="baz" for="foo-1">
          One<input type="radio" name="foo" id="foo-1" value="1" />
      </label>
      <br />
      <label class="baz" for="foo-2">
          Two<input type="radio" name="foo" id="foo-2" value="2" checked="checked" />
      </label>
      <br />
      <label class="baz" for="foo-3">
          Three<input type="radio" name="foo" id="foo-3" value="3" />
      </label>
      */

- formReset($name, $value, $attribs): <input type="reset" /> 要素を作成します。

- formSelect($name, $value, $attribs, $options): <select>...</select> ブロックを作成します。 $options の要素ごとに <option> を作成します。 $options は、選択肢の値をキー、ラベルを値とする配列となります。 $value は初期選択状態を設定します。

      // オプション・グループを使用:

      echo $view->formSelect(
          'foo',
          2,
          array(
              'class' => 'baz',
          ),
          array(
              1     => 'One',
              'Two' => array(
                  '2.1' => 'One',
                  '2.2' => 'Two',
                  '2.3' => 'Three',
              ),
              3     => 'Three',
          )
      );

      /*
      出力:
      <select name="foo" id="foo" class="baz">
          <option value="1" label="One">One</option>
          <optgroup id="foo-optgroup-Two" label="Two">
              <option value="2.1" label="One">One</option>
              <option value="2.2" label="Two">Two</option>
              <option value="2.3" label="Three">Three</option>
          </optgroup>
          <option value="3" label="Three">Three</option>
      </select>
      */

      // 'multiple' オプションの例その１:

      echo $this->formSelect(
          'foo[]',
          2,
          null,
          array(
              1 => 'One',
              2 => 'Two',
              3 => 'Three',
          )
      );

      /*
      出力:
      <select name="foo[]" id="foo" multiple="multiple">
          <option value="1" label="One">One</option>
          <option value="2" label="Two" selected="selected">Two</option>
          <option value="3" label="Three">Three</option>
      </select>
      */

      // 'multiple' オプションの例その２:

      echo $this->formSelect(
          'foo',
          array(
              1,
              2,
          ),
          array(
              'multiple' => true,
          ),
          array(
              1 => 'One',
              2 => 'Two',
              3 => 'Three',
          )
      );

      /*
      出力:
      <select name="foo[]" id="foo" multiple="multiple">
          <option value="1" label="One" selected="selected">One</option>
          <option value="2" label="Two" selected="selected">Two</option>
          <option value="3" label="Three">Three</option>
      </select>
      */

- formSubmit($name, $value, $attribs): <input type="submit" /> 要素を作成します。

- formText($name, $value, $attribs): <input type="text" /> 要素を作成します。

- formTextarea($name, $value, $attribs): <textarea>...</textarea> ブロックを作成します。

- url($urlOptions, $name, $reset, $encode): 指定したルートにもとづく URL 文字列を作成します。 `$urlOptions` は、そのルートで使用するキーと値のペアの配列です。

      // オプション無しで使用: (現行のリクエスト: user/id/1)
      echo $this->url();
      // 出力: user/info/id/1

      // URL オプションを設定:
      echo $this->url(
          array('controller' => 'user', 'action' => 'info', 'username' => 'foobar')
      );
      // 出力: user/info/username/foobar

      // ルートを使用:
      $router->addRoute(
          'user',
          new Zend_Controller_Router_Route(
              'user/:username',
              array(
                  'controller' => 'user',
                  'action'     => 'info',
              )
          )
      );

      echo $this->url(array('name' => 'foobar'), 'user');
      // 出力: user/foobar

      // リセットを使用: (現行のリクエスト: user/id/1)
      echo $this->url(array('controller' => 'user', 'action' => 'info'), null, false);
      // 出力: user/info/id/1

      echo $this->url(array('controller' => 'user', 'action' => 'info'), null, true);
      // 出力: user/info

      // エンコードを使用:
      echo $this->url(
          array('controller' => 'user', 'action' => 'info', 'username' => 'John Doe'), null, true, false
      );
      // 出力: user/info/username/John Doe

      echo $this->url(
          array('controller' => 'user', 'action' => 'info', 'username' => 'John Doe'), null, true, false
      );
      // 出力: user/info/username/John+Doe

- serverUrl($requestUri = null): 現行のサーバーの URL、および任意でリクエスト URI を返すヘルパー。

      // この例の現行のサーバー URL: http://www.example.com/foo.html

      echo $this->serverUrl();
      // 出力: http://www.example.com

      echo $this->serverUrl(true);
      // 出力: http://www.example.com/foo.html

      echo $this->serverUrl('/foo/bar');
      // 出力: http://www.example.com/foo/bar

      echo $this->serverUrl()->getHost();
      // 出力: www.example.com

      echo $this->serverUrl()->getScheme();
      // 出力: http

      $this->serverUrl()->setHost('www.foo.com');
      $this->serverUrl()->setScheme('https');
      echo $this->serverUrl();
      // 出力: https://www.foo.com

- htmlList($items, $ordered, $attribs, $escape): `$items` で渡した内容をもとに 順序つきリストあるいは順序なしリストを作成します。 `$items` が多次元配列の場合は、入れ子状のリストとなります。 `$escape` フラグを `TRUE` (既定値) にすると、 各項目はビューオブジェクトに登録されているエスケープ方式でエスケープされます。 リスト内でマークアップを行いたい場合は `FALSE` を渡します。

これらをビュースクリプト内で使用するのはとても簡単です。 以下に例を示します。ただ単に、ヘルパーをコールするだけでよいことに注意しましょう。 読み込みやインスタンス作成は、必要に応じて自動的に行われます。

    // ビュースクリプト内では、$this は Zend_View のインスタンスを指します。
    //
    // select の選択肢を、変数 $countries に
    // array('us' => 'United States', 'il' => 'Israel', 'de' => 'Germany')
    // として設定済みであることにします。
    ?>
    <form action="action.php" method="post">
        <p><label>メールアドレス:
    <?php echo $this->formText('email', 'you@example.com', array('size' => 32)) ?>
        </label></p>
        <p><label>国:
    <?php echo $this->formSelect('country', 'us', null, $this->countries) ?>
        </label></p>
        <p><label>メールを受け取りますか?
    <?php echo $this->formCheckbox('opt_in', 'yes', null, array('yes', 'no')) ?>
        </label></p>
    </form>

ビュースクリプトの出力結果は、次のようになります。

    <form action="action.php" method="post">
        <p><label>メールアドレス:
            <input type="text" name="email" value="you@example.com" size="32" />
        </label></p>
        <p><label>国:
            <select name="country">
                <option value="us" selected="selected">United States</option>
                <option value="il">Israel</option>
                <option value="de">Germany</option>
            </select>
        </label></p>
        <p><label>メールを受け取りますか?
            <input type="hidden" name="opt_in" value="no" />
            <input type="checkbox" name="opt_in" value="yes" checked="checked" />
        </label></p>
    </form>

## ヘルパーのパス

ビュースクリプトと同様、 Zend_View がヘルパークラスを探すパスをコントローラから積み重ねて指定できます。 デフォルトでは、Zend_View は "Zend/View/Helper/*" からヘルパークラスを探します。 Zend_View に別の場所を探すように指定するには setHelperPath() および addHelperPath() メソッドを使用します。 さらに、クラスプレフィックスを指定することもできます。 これにより、ヘルパークラスに名前空間を設定できるようになります。 デフォルトでクラスプレフィックスを指定しなかった場合は、 'Zend_View_Helper_' であると見なされます。

    $view = new Zend_View();

    // パスを /path/to/more/helpers 、プレフィックスを 'My_View_Helper' と設定します
    $view->setHelperPath('/path/to/more/helpers', 'My_View_Helper');

addHelperPath() メソッドを使用すると、検索パスを「積み重ねる」 ことができます。これを使用すると、Zend_View は一番最後に追加されたパスからヘルパークラスを探し始めます。 これにより、付属しているヘルパーの内容を上書きしたり、 新しいヘルパーを追加したりすることができるようになります。

    $view = new Zend_View();
    // /path/to/some/helpers をクラスプレフィックス 'My_View_Helper' で追加します
    $view->addHelperPath('/path/to/some/helpers', 'My_View_Helper');
    // /other/path/to/helpers をクラスプレフィックス 'Your_View_Helper' で追加します
    $view->addHelperPath('/other/path/to/helpers', 'Your_View_Helper');

    // $this->helperName() をコールすると、Zend_View は
    // まず最初に "/path/to/some/helpers/HelperName" で
    // "Your_View_Helper_HelperName" という名前のクラスを探し、
    // 次に "/other/path/to/helpers/HelperName.php" で
    // "My_View_Helper_HelperName" という名前のクラスを探し、
    // そして最後に "Zend/View/Helpers/HelperName.php" で
    // "Zend_View_Helper_HelperName" という名前のクラスを探します。

## 独自のヘルパーを書く

独自のヘルパーを書くのは簡単です。以下の規則に従ってください。

- 絶対条件というわけではありませんが、ヘルパーを作成する際には Zend_View_Helper_Interface を実装するか Zend_View_Helper_Abstract を継承することを推奨します。 1.6.0 以降、これらには setView() メソッドが定義されています。 しかし、将来のリリースでは Strategy パターンを実装することを検討しており、 以下に示す命名規約の多くを単純化する予定です。 今のうちにこのようにしておくと、 将来のバージョンでもあなたの書いたコードがそのまま動くようになるでしょう。

- クラス名は、少なくとも最後はヘルパーの名前と同じである必要があります。 MixedCaps 方式を使用します。たとえば "specialPurpose" という名前のヘルパーを作成した場合は、そのクラス名には 最低限 "SpecialPurpose" が含まれている必要があります。 このクラス名にプレフィックスを指定できます。 プレフィックスの一部に 'View_Helper' を含めることを推奨します。たとえば "My_View_Helper_SpecialPurpose" のようになります (addHelperPath() や setHelperPath() にはプレフィックスを指定する必要があります。 最後のアンダースコアは含めても含めなくてもかまいません)。

- クラスは、ヘルパーと同じ名前の public メソッドを持っている必要があります。 テンプレートが "$this->specialPurpose()" をコールした際に、 このメソッドがコールされます。"specialPurpose" ヘルパーの例では、 "public function specialPurpose()" というメソッドが必要です。

- 一般に、クラスでは echo や print その他の出力を行ってはいけません。 その代わりに、print あるいは echo される内容を返します。 返り値は、適切にエスケープしなければなりません。

- クラスは、ヘルパークラスと同じ名前のファイルに作成しなければなりません。 再び "specialPurpose" ヘルパーを例にとると、ファイル名は "SpecialPurpose.php" でなければなりません。

指定したヘルパーパスのどこかにヘルパークラスのファイルを配置すると、 Zend_View は自動的にそれを読み込んでインスタンスを作成し、 必要に応じて実行します。

SpecialPurpose ヘルパーのコードの例を示します。

    class My_View_Helper_SpecialPurpose extends Zend_View_Helper_Abstract
    {
        protected $_count = 0;
        public function specialPurpose()
        {
            $this->_count++;
            $output = "'The Jerk' を {$this->_count} 回見ました。";
            return htmlspecialchars($output);
        }
    }

そして、ビュースクリプト内で SpecialPurpose ヘルパーを必要なだけコールします。いちどインスタンスが作成された後は、 Zend_View インスタンスの中でそれが持続します。

    // ビュースクリプト内では、$this は Zend_View インスタンスを指すことを覚えておきましょう。
    echo $this->specialPurpose();
    echo $this->specialPurpose();
    echo $this->specialPurpose();

出力結果は、次のようになります。

    'The Jerk' を 1 回見ました。
    'The Jerk' を 2 回見ました。
    'The Jerk' を 3 回見ました。

時には Zend_View オブジェクトを使用したくなることもあるでしょう。 たとえば登録されているエンコーディングを使用する必要があったり、 ヘルパー内で別のビュースクリプトをレンダリングしたくなったりといった場合です。 ビューオブジェクトにアクセスするには、ヘルパークラス内で次のような setView($view) メソッドを定義しなければなりません。

    class My_View_Helper_ScriptPath
    {
        public $view;

        public function setView(Zend_View_Interface $view)
        {
            $this->view = $view;
        }

        public function scriptPath($script)
        {
            return $this->view->getScriptPath($script);
        }
    }

ヘルパークラスで setView() メソッドを定義しておくと、 最初にインスタンスが作成される際に自動的にこのメソッドがコールされ、 現在のビューオブジェクトが引数として渡されます。 渡されたオブジェクトをクラス内でどのように管理するかは特に決まっていません。 お好みの方法で管理してください。

Zend_View_Helper_Abstract を継承する場合は、 このメソッドはすでに定義済みであるため定義する必要はありません。

## 実在するヘルパーを登録

時には、ビュー・ヘルパーをインスタンス化して、それからビューで登録すると便利です。 バージョン 1.10.0 時点で、これは registerHelper() メソッドを用いると可能です。 それは２つの引数を期待します。それは、ヘルパー・オブジェクトと登録される名前です。

    $helper = new My_Helper_Foo();
    // ...何らかの構成または依存性注入を行います...

    $view->registerHelper($helper, 'foo');

ヘルパーに setView() メソッドがあると、 ビュー・オブジェクトはこれを呼び出して、登録時に自分自身をヘルパーに注入します。

ヘルパー名はメソッドと一致しなければいけません

registerHelper() の２番目の引数はヘルパー名です。 対応するメソッド名がヘルパー内に存在しなければいけません。 さもなければ、ヘルパー起動時に Zend_View が存在しないメソッドを呼び出して 致命的な PHP エラーを引き起こします。