cakephp3 Controllerを階層化する際の方法
ちょっとした決済システムを構築しています。
販売者(merchants)と顧客(customers)がいて、
決済(orders)を通じてデータが紐付いています。
それぞれにMVC(Model,View,Controller)があるわけですが、
販売者、顧客が自身の決済を参照するために、
管理画面を用意する必要があります。
そうなると、
販売者はmerchants/merchant:id/orders/index
顧客はcustomers/customer:id/orders/index
で決済を見るようにしたいわけです。
それを実現するために、
Controllerを階層化したいのですが、
Google先生で調べながらやると、
迷走してしまいました^^;
迷走した理由は、
2つの情報が錯綜しているからなのですが、
それは後述するとして、
先に階層化する方法を書きます。
Controllerを階層化する際の方法
(1) routes.phpに以下を追記する
>|php|
Router::prefix('merchants',function($routes){
$routes->fallbacks(DashedRoute::class);
});
|
(2) Controller/Merchants/OrdersController.phpを作成する
<?php namespace App\Controller\Merchants; use App\Controller\AppController; /** * Merchants\Orders Controller * * @property \App\Model\Table\OrdersTable $Orders * * @method \App\Model\Entity\Order[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = []) */ class OrdersController extends AppController { ... /** * Index method * * @return \Cake\Http\Response|void */ public function index(){ } ... }
(3) Template/Merchants/Orders/index.ctpを作成する
以外と簡単な設定ですみました。
ちなみにログイン画面は、
merchants/loginでアクセスしたいと考えた場合、
上記のroutes.phpの設定では、
merchantsController.phpを探しに行くのではなく、
src/Controller/Merchants/LoginController.phpを
探しにいってしまいます。
MerchantsController::loginの方に
アクセスしたい場合は、
routes.phpに
$routes->connect('/merchants/login', ['controller' => 'Merchants','action' => 'login', 'prefix' => null]);
と記載すればOKです。
[補足] Google検索したら迷走した理由
階層化は前述の通り比較的簡単な設定ですみましたが、
実際ここにたどり着くためには、1,2時間要してしましました。
理由は2つの情報が錯綜していたからです。
2つの情報は、それぞれ以下です。
▼1つ目(Controllerを階層化する方法)
teratail.com
▼2つ目(階層化した時にController名がかぶった場合の設定)
norm-nois.com
1つ目の情報によると、
Controllerを階層化するには、
composer.jsonを修正して、
src/Controller/Merchants/ を自動読み込みすればよい
ということが書いてあります。
2つ目の情報によると、
src/Controller/UsersController.php
src/Controller/Admins/UsersController.php
のように別階層に同じ名前のControllerを置く場合は、
bootstarp.phpに数行その旨を記述しないといけない
ということが書いてあります。
最初に見つけた情報が
1つ目の記事だったので、
書いてあるとおりに実行をしたら、
Controllerを階層化することには成功しました。
しかし、階層構造の中に
同じ名前のコントローラが出てくると
片方のコントローラが読み込みエラーになります。
そのため、
2つ目の記事にある
src/Controller/UsersController.php
src/Controller/Admins/UsersController.php
という階層構造を作る方法を実践してみたのですが、
こちらはうまくいきませんでした。
最終的に、
1つ目の設定を解除して
routes.phpで公式ドキュメントにある
プレフィックスルーティングの設定をすることで
解決したというところになります。
cakephp3でフォームの画面遷移のロジックをうまく整理できた
cakephp3を使用してフォームを書く度に、
画面遷移のロジックを綺麗に書けずといいますか、
納得した形で書けず、ずっと悩んでおりました。
悩みながらネットで調べていたところ
POSTで画面遷移をうまく切り替えるプラグインを
書いている方がいらっしゃいまして、
その方のコードを拝借することにしました。
詳細は以下のQiitaのページをご覧ください。
qiita.com
ただ、composerでインストールして
そのままで利用すると、
なぜか私の環境ではうまく動作しなかったので、
エラーがあるごとにプラグインのコードを修正いたしました。
一部自分仕様に仕上げたところもあり、
オリジナルと動作が異なる部分があります。
修正版のコードを
備忘録として挙げておきます。
以下、PostTransitionControllerTrait.php の修正版
<?php namespace PostTransition\Controller; use Cake\Utility\Security; use Cake\Utility\Hash; use Cake\Routing\Router; use Cake\Network\Exception\MethodNotAllowedException; use Cake\ORM\TableRegistry; trait PostTransitionControllerTrait { private $__default_settings = [ 'nextPrefix' => 'next', 'backPrefix' => 'back', 'nowField' => 'now', 'default' => [ 'value' => [], ], 'post' => [], 'param' => [], ]; private $__settings; public $transitionModel; private function __postTransition($settings){ //設定値の設定 $this->__postSettingAdjustment($settings); if (empty($this->__settings['model'])){ $this->__settings['model'] = $this->modelClass; } $this->transitionModel = TableRegistry::get($this->__settings['model']); //post_max_sizeオーバーのときのエラーチェック //postでかつデータが空の時=>post_max_file_sizeオーバー時の対応 if ($this->request->is('post') && empty($this->request->data)) { $this->Flash->error(__d('post_transition', 'Post max size error.')); $this->__sessionTimeout(); } //初期アクセス時の対応 if (!$this->request->is('post') && !$this->request->is('put')){ $this->__firstAction(); return; } //セッション切れの処理 if (!$this->request->session()->check($this->__settings['model'] . '.' . $this->request->data['hidden_key'])){ //$this->Flash->error(__d('post_transition', 'Session Timeout.')); return $this->__sessionTimeout(); } //request->dataで来たデータから必要なprefixのついているものを抽出 $keys = array_keys($this->request->data); $action_button_check = preg_grep('/^(' . $this->__settings['nextPrefix'] . '|' . $this->__settings['backPrefix'] . ')_/',$keys); if (empty($action_button_check)){ //エラー throw new MethodNotAllowedException(); } //一番目のものを取得する(複数はない前提) $action_data = array_shift($action_button_check); //next_action if (!preg_match('/^(' . $this->__settings['nextPrefix'] . '|' . $this->__settings['backPrefix'] . ')_(.*)$/',$action_data, $action)){ //上部マッチで取っているはずだがもし流れた場合はエラー throw new MethodNotAllowedException(); } $entity_data = $this->request->session()->read($this->__settings['model'] . '.' . $this->request->data['hidden_key']); $entity = $this->transitionModel->newEntity($entity_data); //追記 $entity->now = $entity_data['now']; if (!empty($this->request->data[$this->__settings['nowField']])) { $nowField = $this->request->data[$this->__settings['nowField']]; } else { $nowField = $entity->{$this->__settings['nowField']}; } //何も設定がないときはdefaultを読む $validate_option = []; if (array_key_exists('validate_option', $this->__settings['post'][$nowField])){ $validate_option = $this->__settings['post'][$nowField]['validate_option']; } $entity = $this->transitionModel->patchEntity($entity, $this->request->data(), $validate_option); if ( $action[1] == $this->__settings['nextPrefix'] && $entity->errors() ){ //$this->Flash->error(__d('post_transition', 'Validation could not pass.')); $mergedData = array_merge( $entity_data, $this->request->data() ); $this->request->session()->write($this->__settings['model'] . '.' . $this->request->data['hidden_key'], $mergedData); $this->_viewRender($entity, $entity->{$this->__settings['nowField']}, $this->__settings['param']); return; } $mergedData = array_merge( $this->request->data, [$this->__settings['nowField'] => $action[2]] ); $entity = $this->transitionModel->patchEntity($entity, $mergedData); $mergedData = array_merge( $entity_data, $mergedData ); $this->request->session()->write($this->__settings['model'] . '.' . $this->request->data['hidden_key'], $mergedData); $this->_viewRender($entity, $action[2], $this->__settings['param']); return; } protected function _viewRender($entity, $action, $param){ $private_method = $this->__settings['post'][$action]['private']; if (method_exists($this, $private_method)){ $this->{$private_method}($entity, $param); } //viewのnowに現在の画面の値をセットする $entity->{$this->__settings['nowField']} = $action; $this->set(compact('entity')); if ($this->__settings['post'][$action]['render'] !== false){ $this->render($this->__settings['post'][$action]['render']); } return; } private function __postSettingAdjustment($settings){ $this->__settings = Hash::merge( $this->__default_settings, $settings ); foreach ($this->__settings['post'] as $post_key => $post_val){ if (is_string($post_val)){ $this->__settings['post'][$post_val] = [ 'render' => $post_val, 'private' => '__' . $post_val, 'validate_option' => [], ]; //不要なものは削除 unset($this->__settings['post'][$post_key]); } else { if (!array_key_exists('render', $post_val)){ $this->__settings['post'][$post_key]['render'] = $post_key; } if (!array_key_exists('private', $post_val)){ $this->__settings['post'][$post_key]['private'] = '__' . $post_key; } if (!array_key_exists('validate_option', $post_val)){ $this->__settings['post'][$post_key]['validate_option'] = []; } } } } private function __firstAction(){ $hidden_key = Security::hash(time() . rand()); if (is_object($this->__settings['default']['value'])){ $entity = $this->__settings['default']['value']; $entity->hidden_key = $hidden_key; $value = $entity->toArray(); } else { /* $value = array_merge( $this->__settings['default']['value'], ['hidden_key' => $hidden_key] ); */ $value = $this->__settings['default']['value']; $entity = $this->transitionModel->newEntity($value); $entity->hidden_key = $hidden_key; } //セッションに空のデータを作成しておく $entity->{$this->__settings['nowField']} = $this->__settings['default']['post_setting']; $value[$this->__settings['nowField']] = $this->__settings['default']['post_setting']; $this->request->session()->write($this->__settings['model'] . '.' . $hidden_key, $value); $this->_viewRender($entity, $this->__settings['default']['post_setting'], $this->__settings['param']); } private function __sessionTimeout(){ //最初に戻る if (!empty($this->__settings['start_action'])){ $start_action = $this->__settings['start_action']; } else { //設定がないときは自身のURLにリダイレクト(基本こっち //自身のURLがうまくRouter::urlで取れないので自身で作成しておく $start_action = Router::fullBaseUrl() . $this->request->here; } return $this->redirect($start_action); } }
bitcoindに秘密鍵をインポートしたのに残高が反映されない件
表題の通りなのですが、
bitcoindに秘密鍵をインポートしたのに、
残高が0のままで焦りました。
その対応の記録です。
同じ問題で困っている方の一助になればと思います。
(1)秘密鍵をインポートする
念の為、秘密鍵のインポートの仕方を記載します。
bitcoindを立ち上げた後、以下のコマンドでインポートされます。
$bitcoin-cli importprivkey "bitcoinprivkey" ( "label" rescan)
引数に関して以下に解説を加えます。
引数名 | 解説 |
---|---|
bitcoinprivkey | 秘密鍵のこと。ただしWIF形式で指定します。 |
label (任意) | アカウント名を指定します。 |
rescan(任意) | ブロックチェーンを再読込する場合はtrueを指定します。 |
というところです。
最初rescanのことをうまく認識できておらず、
これが今回の問題の一番の要因となりました。。。
(3)ウォレットは秘密鍵をインポートした後の取引しか見ない
焦りながら情報を調べていった結果、
以下の記事を見つけ、
bitcoin coreのウォレットは、
"秘密鍵をインポートした後の取引しか見ない"ということが分かりました。
bitcoin.stackexchange.com
(4)インポートの前の履歴や残高を取得する場合
bitcoindにブロックチェーンを再読込するように
教えてあげないといけません。
コマンドは簡単で、
$ bitcoind -rescan
です。
再読込のため、
私のメモリ8GBのPCでは、
2時間弱かかりましたが、無事残高を正しく表示できました。
なお、bitcoin-cli importprivkey の引数の一つであるrescanについてです。
ここをtrueで指定してやると、勝手にブロックチェーンを再読込しそうなのですが、
機能しないようです。
以下の記事でそのようなことが報告されています。
github.com
これで無事、秘密鍵と残高が反映されました^^
Ubuntu 16.04 LTSのキーボードを日本語に変更する
[背景]
Lispを勉強するために
MacにUbuntu on VirtualBoxを入れた。
しかし、入力が英語キーボードのため日本語キーボードに修正したい。
[解決]
以下のページを参照することで解決した。
電子マネーは分裂しないのにビットコインは分裂する理由
cakephp3でフォームに確認画面を用意する(どのようにしてvalidationを適用するか?)
こんばんは。「がの」です。
最近cakephp3を触ってます。
今回の記事は
validationについてです。
確認画面を要するフォーム作成時に
validationで少し躓いたので、
その備忘録を書きます。
ーーーー【達成したかったこと】ーーーー
(1)フォームに入力した項目を
TableのvalidationDefaultでチェックしたい。
(2)入力後、確認画面を表示した上で、
データを保存する。
(3)bakeで生成されるaddアクションを
見ると $this->Table->save($entity)で
validationDefaultが発動しているが、
saveの手前の「確認画面表示」の時に、
validationを行いたい。
ーーーーーーーーーーーーーーーーーーー
cakephp2での記述の仕方は
ググれば色々書いてあるのですが、
cakephp3は記事が少なく、
最初よく分かりませんでした。
調べていて分かったことは、
”cakephp3では、INSERTを行う際に、
2度validationをかけている"
ということです。
2回というのは、
それぞれ以下になります。
ーーーーーーーーーーーーーーーーーーー
[1回目]
$entity = $this->Table->newEntity($postParams)
で、newEntityを作る時。
[2回目] 前述の通り
$this->Table->save($entity) の時
ーーーーーーーーーーーーーーーーーーー
通常どおり、プログラムを実行すると、
newEntityの時にvalidationをかけていることは
気づきません。。。
エラーが発生しても、
何の反応やメッセージもなく、
そのままコードを実行するからです。
達成したい内容は、
newEntityの時のvalidationの結果を踏まえて、
エラーが無ければ確認画面へ、、、
エラーが有れば入力画面でエラー表示
とすれば良いでしょう。
肝心のnewEntityの時のvalidation結果は、
$entity->errors()で取得可能です。
errors()が空であれば
確認画面に飛ばせば良いでしょう。
また、validationで指定した
エラーメッセージがerrors()の中に
格納されているので、
errors()が空ではない場合は、
そのままTemplateに渡して、
メッセージを表示させればよいでしょう。
書いたコードは
気が向いたら載せます^^;
以上、
「がの」がお送りしました(^o^)/