Drupal8 FormクラスでGET通信を使う
カスタムモジュールで、Formクラスを使う時の話。
デフォルトはPOST通信になる。
「設定を変えればGET通信なんて余裕だぜ、はははー」
なんて思いつつ、以下に設定。
<?php $form['#method'] = 'get';
うーん、だめ。動かない。なぜ?
悶々と調べた結果、Drupal8コアのバクとしか思えないんだが…。
以下、コアから引用。
<?php // 'GET' が設定される? $form_state->setRequestMethod($request->getMethod()); // 'GET' 時の分岐 <?php $input = $form_state->isMethodType('get') ? $request->query->all() : $request->request->all();
実際、一つ目のメソッドで 'GET' は設定されない。
以下のように、setRequestMethod ではなく、setMethod を使う必要がある。
<?php $form_state->setMethod($request->getMethod());
ということで、この辺りの処理を、コアから外に出して、手動で行う。
コントローラにて。
<?php $formState = new FormState(); $formState->setMethod('get'); $form = \Drupal::formBuilder()->buildForm('\Drupal\sample_form\Form\ExampleForm', $formState);
上記のように書くと、何事も無かったかのように、FormクラスがGET通信で動く様になる。
JavaScriptのコンストラクタとプライベート変数
まず、コンストラクタを使うこと前提で。
コンストラクタ内でメソッドを定義する場合、プライベート変数を使うことができる。
よくあるカウント機能を持つサンプル。
function Nogi() { var count = 0; this.say = function () { count++; console.log(count); }; }
インスタンス毎に、プライベート変数 count が別々にカウントアップされていることが分かる。
var nogi = new Nogi(); nogi.say(); // 1 nogi.say(); // 2 nogi.say(); // 3 var nogi2 = new Nogi(); nogi2.say(); // 1 nogi2.say(); // 2 nogi2.say(); // 3
しかし、インスタンスを作るごとに、this.say()がコピーされてしまう。
普通、メソッドは prototype に定義する。
だが、prototypeでメソッドを定義する場合、プライベート変数を使うことはできない。
それっぽいことをやってみる。
function Nogi() {} Nogi.prototype.say = (function () { var count = 0; return function () { count++; console.log(count); } }());
インスタンスを複数作り、カウントアップ。
var nogi = new Nogi(); nogi.say(); // 1 nogi.say(); // 2 nogi.say(); // 3 var nogi2 = new Nogi(); nogi2.say(); // 4 nogi2.say(); // 5 nogi2.say(); // 6
変数 count が共用されている。そりゃ prototype 自体が共用されているので当たり前か。
count はクラス変数になっているとも言える。
ES6ならやりたいことが出来るかも。
でも今はES6のことは考えたくありません。さようなら。
Observerパターンについて考える
Observerパターン、名前は知ってるけど、いまいち使いどころがよく分かりませんでした。
「状態の変化を通知する仕組み」とのことですが、あまりそういう状況に出くわさないというか、頭にピンとこないというか…。
調べてみたんですが、おそらく自分は勘違いしてました。
これ、別に通知とか関係ないのでは。
結局のところ、
<?php $objects = [ new ObjectA(), new ObjectB(), new ObjectC() ]; foreach ($objects as $object) { $object->commonMethod(); }
上記のように、「同名のメソッドを持つなら、ループできるよ」というだけのように思います。
<?php interface Object { public function commonMethod(); }
インターフェースを作って、共通のメソッドを持たせます。
そしてもう一点、「状態変化をどのObserverに通知するか(共通メソッドをどのオブジェクトに実行させるか)」ですが、新人の頃の自分なら、わざわざフラグ用の変数を作っていたように思います。
<?php $objectA->isObserving = true; $objectB->isObserving = false; $objectC->isObserving = true; $objects = [$objectA, $objectB, $objectC]; foreach ($objects as $object) { if ($object->isObserving) { $object->commonMethod(); } }
でもそうじゃなく、Subject(メインルーチン)にObserver(メソッドを実行させるオブジェクト)を配列で持たせてやれば、それでいいんですよね。
<?php $subject->observerList = [ $ObjectA, $ObjectC ]; foreach ($subject->observerList as $object) { $object->commonMethod(); }
フラグ用の変数を作るのは、今後控えようと思います。フラグを立てたいオブジェクトを配列に持たせてやれば、それでよい!
結局、JavaScriptはどう継承すればいいのか
結局、JavaScriptはどう継承すればいいのか。
調べてもサイトによって書いてる事が違うので、ひとまず、信用できそうな方法を以下にまとめます。
(非 ES6 です。)
乃木坂46の秋元真夏を親クラス、相楽伊織を子クラスとします。
2期生の相楽は、1期生秋元の意思を継いでるわけですね。
prototype継承用の関数。
一時的なプロキシコンストラクタを使います。
// prototype継承用の関数 var inherit = (function() { var F = function() {}; // クロージャを利用 return function(C, P) { F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; } }());
こちら、親クラス。
// 親クラス var Parent = function() { this.group = '乃木坂46'; this.name = '秋元真夏'; } Parent.prototype.shoot = function() { console.log(this.group + 'の' + this.name + 'です。ズッキュン!'); }
子クラスです。
applyメソッドで、親コンストラクタのプロパティを継承。
inherit関数で、親のprototypeに設定されたメソッドを継承してます。
// 子クラス var Child = function() { Parent.apply(this, arguments); // 親のプロパティを継承 this.name = '相楽伊織'; } inherit(Child, Parent); // 親のメソッドを継承
実行。相楽伊織がズッキュンを継承してくれました。
var child = new Child; child.shoot(); // 乃木坂46の相楽伊織です。ズッキュン!
うーん、まだprototypeを使いこなしてる感じがしないなぁ。
もう少し粘着して覚えていきます。
デコレータパターンでテンプレートメソッドを使う(PHP)
うーん、今までデコレータパターンを勘違いしてた。
デコレータパターンは継承と書き換え可能でした。
デコレータは動的に継承っぽい関係を作れるので、その点メリットがあります。
ただ、どの程度書き換え可能なのか…?
自分はテンプレートメソッドをよく使うんですが、そういうこともデコレータは出来るのか…?
というわけで、デコレータパターンでテンプレートメソッドを再現してみました。
ひとまず、一旦、普通の継承を。
<?php class Super { public function render() { echo '<p>2</p>'; $this->tmplMethod(); echo '<p>4</p>'; } } class Child extends Super { public function render() { echo '<p>1</p>'; parent::render(); echo '<p>5</p>'; } public function tmplMethod() { echo '<p>3</p>'; } } $obj = new Child(); $obj->render();
こいつを、デコレータで書き換える。
親となるクラス。
<?php class Super { public function render(callable $callback) { echo '<p>2</p>'; $callback(); echo '<p>4</p>'; } }
子供のクラス。
<?php class Child { public function __construct(Super $super) { $this->super = $super; } public function render() { echo '<p>1</p>'; $this->super->render([$this, 'tmplMethod']); echo '<p>5</p>'; } public function tmplMethod() { echo '<p>3</p>'; } }
実行。
<?php $obj = new Child(new Super); $obj->render();
うーん、引数でコールバックを渡すくらいしか、思いつかないなぁ。
ひとまず、この方法で使っていきます。
JavaScriptを超シンプルなMVCで書く(2)
以前の記事「JavaScriptを超シンプルなMVCで書く」を見返すとイマイチなので書き直し。
lonely-programmer.hatenablog.jp
以下挙動のアプリケーションを作ります。
・ボタンをクリックする毎に、白石麻衣の所属するグループが、乃木坂46 / 欅坂46 / SKE46 の順番でループする。
まずモデル。
var model = (function() { /* privateメンバ */ var count = 0; var groups = []; // 現在のグループを取得 var getNowGroup = function() { return groups[getIndex()]; }; // 現在のインデックスを取得 var getIndex = function() { // 0 → 1 → 2 → 0 → 1 → 2... とインデックスをループ return count % groups.length }; /* publicメンバ */ // グループを追加 var addGroup = function(group) { groups.push(group); }; // グループを変更 var changeHerGroup = function() { count++; $(this).trigger('groupIsChanged'); }; // グループ名を表示 var getGroupName = function () { return getNowGroup().getName(); }; // publicメンバを開示 return { addGroup: addGroup, changeHerGroup: changeHerGroup, getGroupName: getGroupName }; })();
モジュールパターンでオブジェクトを返却してます。
このオブジェクトはクロージャです。
privateメンバには外部からアクセスできません。
注目すべきはここ。
var changeHerGroup = function() { count++; $(this).trigger('groupIsChanged'); };
モデル内の値に変化があった場合、'groupIsChanged'イベントが発火されます。
オブジェクトではなく、コンストラクタ関数で返却する場合は、メソッド後半を以下のように書き変えます。
// コンストラクタ関数にpublicメンバを登録 var Constr = function() { this.addGroup = addGroup; this.changeHerGroup = changeHerGroup; }; // プロトタイプにpublicメンバを登録 Constr.prototype = { getGroupName: getGroupName }; // コンストラクタ関数を返却 return Constr;
グループ用のオブジェクトをモデルのプロパティに追加。
変数nameはprivateです。
// グループ用のコンストラクタ関数 function Group(name) { var name = name; // 書かなくても良い this.getName = function() { return name; }; } // グループ追加 model.addGroup(new Group('乃木坂46')); model.addGroup(new Group('欅坂46')); model.addGroup(new Group('SKE48'));
ボタンがクリックされると、モデル内の値を更新。
// ボタンクリック時のイベントハンドラ $('button').on('click', function() { model.changeHerGroup(); });
カスタムイベントを作成。
// レンダリング用カスタムイベント $('span').on('render', function() { $(this).text(model.getGroupName()); });
モデル内の値に変化があれば、再レンダリングを行います。
jQueryのon()メソッドは、DOMオブジェクト以外も監視できることを最近知りました…。
// モデルを監視 $(model).on('groupIsChanged', function() { // テキストを再レンダリング $('span').trigger('render'); });
こちら、初期表示。
// テキストを初期表示 $('span').trigger('render'); // JavaScript処理完了後に画面表示 $('body').css('display', 'block');
ES6のことは一切考慮してません。笑
次回以降の記事で、ES6に書き換えてみようと思います。
以下、全コード。
<html> <script src="jquery.js"></script> <script> $(function() { var model = (function() { /* privateメンバ */ var count = 0; var groups = []; // 現在のグループを取得 var getNowGroup = function() { return groups[getIndex()]; }; // 現在のインデックスを取得 var getIndex = function() { // 0 → 1 → 2 → 0 → 1 → 2... とインデックスをループ return count % groups.length }; /* publicメンバ */ // グループを追加 var addGroup = function(group) { groups.push(group); }; // グループを変更 var changeHerGroup = function() { count++; $(this).trigger('groupIsChanged'); }; // グループ名を表示 var getGroupName = function () { return getNowGroup().getName(); }; // publicメンバを開示 return { addGroup: addGroup, changeHerGroup: changeHerGroup, getGroupName: getGroupName }; })(); // Controller (function() { // グループ用のコンストラクタ関数 function Group(name) { var name = name; // 書かなくても良い this.getName = function() { return name; }; } // グループ追加 model.addGroup(new Group('乃木坂46')); model.addGroup(new Group('欅坂46')); model.addGroup(new Group('SKE48')); // ボタンクリック時のイベントハンドラ $('button').on('click', function() { model.changeHerGroup(); }); })(); // View (function() { // モデルを監視 $(model).on('groupIsChanged', function() { // テキストを再レンダリング $('span').trigger('render'); }); // レンダリング用カスタムイベント $('span').on('render', function() { $(this).text(model.getGroupName()); }); // テキストを初期表示 $('span').trigger('render'); // JavaScript処理完了後に画面表示 $('body').css('display', 'block'); })(); }); </script> <body style="display: none"> <button>グループ変更</button> <p><span></span>の白石麻衣です。</p> </body> </html>
Riot.jsでモデルっぽいものを書いてみる
Riot.jsを初めて触ってみた。
あまりにシンプル過ぎて驚いた!
ほとんど覚えること無しに、それなりに書けそう。
というわけで、前回・前々回の流れで、簡単なサンプルをRiot.jsで書いてみます。
悩んだのが、Riot.jsにはモデルが見当たらない…?
おそらく、Observableがそれにあたるのか…?
とりあえず、モデルっぽいものを使ってみることにします。
HTMLはこれだけ。
<html> <body> <div id="nogi"></div> <div id="nogi-text"></div> <script src='https://cdnjs.cloudflare.com/ajax/libs/riot/2.3.18/riot+compiler.js'></script> </body> </html>
モデルの発火、監視を可能にする。
<script> var model = { isNogi: true } riot.observable(model); </script>
これが、カスタムタグ。
HTMLタグと、JavaScriptとが合体してるイメージ。スタイルシートも定義できます。
「このタグはこういう挙動をする」ってのが見て一発で分かる。
わざわざソースコードを追いかけ回す必要もない。便利だなぁ…。
<script type='riot/tag'> <nogi> <button onclick='{ invert }'>{ name }</button> // モデルを更新 this.invert = function () { model.isNogi = !model.isNogi; // 通知イベント発火 model.trigger('hasInverted'); }; // 通知イベントを検知して、要素を描画 model.on('hasInverted', (function () { this.render(); }).bind(this)); this.render = function () { this.name = model.isNogi ? '乃木坂46' : '欅坂46'; }; // 初期描画 this.render(); </nogi> </script>
こちら、別のカスタムタグ。
モデルの発火を検知して、再レンダリングする。
<nogi-text> <p>{ description }</p> // 通知イベントを検知して、要素を描画 model.on('hasInverted', (function () { this.render(); }).bind(this)); this.render = function () { this.description = model.isNogi ? '2011年に結成されました。' : '2015年に結成されました。'; this.update(); }; // 初期描画 this.render(); </nogi-text>
マウントする。マウントって何?
カスタムタグと関連付けます。
<script> // マウント riot.mount('#nogi', 'nogi'); riot.mount('#nogi-text', 'nogi-text'); </script>
覚えるのも書くのも簡単そうなので、せめて公式ドキュメントは全部読んでおこうと思います。
低コストで最新のJavaScriptフレームワークを身につけられる。お得ですなぁ…。