Component Model
Nette の大事な考え方のひとつがコンポーネントです。ページには目に見える対話的なコンポーネントを差し込みます。フォームとそのすべての要素もコンポーネントです。これらすべてのコンポーネントが継承する
2 つの基本のクラスは nette/component-model
のパッケージの一部で、コンポーネントの木の階層を作ることを受け持ちます。
Component
Nette\ComponentModel\Componentはすべてのコンポーネントの共通の祖先です。コンポーネントの名前を返す
getName() メソッドと、その親を返す getParent() メソッドを持ちます。どちらも
setParent() メソッドで設定できます。第 1 パラメータが親、第 2
パラメータがコンポーネントの名前です。
lookup (?string $type, bool $throw=true): ?Component
階層を上へたどって、目当てのクラスやインターフェースのオブジェクトを探します。たとえば
$component->lookup(Nette\Application\UI\Presenter::class)
は、そのコンポーネントがプレゼンターにつながっていれば、何段か上でもそのプレゼンターを返します。合うオブジェクトが見つからなければ例外を投げます。代わりに
null を返させたいなら、第 2 引数に false を渡します。$type に
null
を渡すと、このメソッドは木のいちばん上のコンポーネント、つまり親のない根を探します。
lookupPath (?string $type=null, bool $throw=true): ?string
いわゆるパスを返します。これは今のコンポーネントと探しているコンポーネントのあいだの道すじにあるすべてのコンポーネントの名前をつないだ文字列です。ですからたとえば
$component->lookupPath(Nette\Application\UI\Presenter::class)
は、プレゼンターから見たそのコンポーネントの一意の識別子を返します。$type が
null の場合(または省いた場合)、パスは木の根まで測られます。
Container
Nette\ComponentModel\Containerは親のコンポーネント、つまり子を含んで木の構造を作るコンポーネントです。オブジェクトを簡単に足し、取り出し、取り除くメソッドを持ちます。たとえばフォームや
Control、Presenter のクラスの祖先です。ArrayAccess
のトレイトを使う子孫(Control や Presenter
など)では、$container['child'] のように配列の書き方で子に触れられます。
addComponent (Component $component, ?string $name, ?string $insertBefore=null): static
コンテナに子としてコンポーネントを足します。$name が null
なら、そのコンポーネント自身の名前が使われます。省略できる
$insertBefore(既存の子の名前)を使うと、新しいコンポーネントはそのすぐ前に差し込まれます。そうでなければ最後に足されます。このメソッドはコンテナ自身を返すので、呼び出しをつなげられます。
removeComponent (Component $component): void
コンテナから子のコンポーネントを取り除きます。
getComponent (string $name): ?Component
コンポーネントを返します。定義されていない子を取り出そうとすると、ファクトリメソッド
createComponent($name) が呼ばれます。createComponent($name)
メソッドは、今のコンポーネントの createComponent<コンポーネントの名前>
というメソッドを、コンポーネントの名前をパラメータとして渡して呼びます。作られたコンポーネントは、そのあと今のコンポーネントの子として足されます。これらのメソッドをコンポーネントのファクトリと呼び、Container
を継承したクラスで実装できます。
getComponents(): IComponent[]
直接の子孫を配列として返します。キーにはそれらのコンポーネントの名前が入ります。部分木全体を再帰的に取り出すには
getComponentTree() を、型で絞るなら array_filter()
と組み合わせて使ってください。(古い版で知られていた $deep と $filterType
のパラメータは、バージョン 4.0 で取り除かれました。)
getComponentTree(): list<IComponent>
入れ子のすべての子のコンポーネントも含めた、コンポーネントの階層全体を添字の配列として取り出します。探索は深さ優先です。
祖先を見張る
Nette のコンポーネントのしくみは木をとても動的に扱えるので(コンポーネントを取り除いたり、移したり、足したりできます)、コンポーネントを作った直後(コンストラクタの中)に親や親の親などが分かっていると当てにするのは誤りです。ふつう、コンポーネントが作られる時点で親はまったく分かっていません。
では、コンポーネントは自分がプレゼンターの下(あるいは、ある型のほかの祖先の下)に取り付けられた瞬間をどう知るのでしょうか。直接の親を見張るだけでは足りません。つながりは木のもっと上で、たとえば親の親が取り付けられたときに起こるかもしれないからです。そのために
monitor($type,
$attached, $detached)メソッドがあります。コンポーネントは、クラスやインターフェース
$type
の祖先が木の中で自分の上に現れたとき、あるいはそこから消えたときに知らせてほしいと申し出ます。コンポーネントはいくつでも型を見張れます。$attached
のコールバックは合う祖先がつながったときに発火し、その祖先を引数として受け取ります。$detached
は切り離されたときに発火します。見張るのは unmonitor($type) でやめられます。
知らせは木の構造に沿って届きます。取り付けのときは祖先が子孫より先に知らされるので(上から下へ)、親が共有の状態を先に整えたり、子自身のコールバックが走る前にその子を取り除いたりできます。取り外しのときは順序が逆で、子孫が先に知らされます。コールバックの重複も取り除かれるので、同じオブジェクトについて同じコールバックが二度呼ばれることはありません。この振る舞いの理由は、バージョン 4.0 のブログ記事をご覧ください。
分かりやすくするために例を挙げます。Nette Forms
でファイルをアップロードするフォームの要素を表す UploadControl クラスは、フォームの
enctype の属性を multipart/form-data
にしなければなりません。しかしそのオブジェクトが作られる時点では、どのフォームにも取り付けられていないかもしれません。ではどの時点でフォームに手を入れればよいのでしょうか。解は簡単で、コンストラクタで見張りを申し出るのです。
class UploadControl extends Nette\Forms\Controls\BaseControl
{
public function __construct($label)
{
$this->monitor(Nette\Forms\Form::class, function ($form): void {
$form->setHtmlAttribute('enctype', 'multipart/form-data');
});
// ...
}
// ...
}
そしてフォームが使えるようになったとたん、そのコールバックが呼ばれます。
新しい版へ上げるなら、アップグレードのページをご覧ください。