ITの隊長のブログ

ITの隊長のブログです。Rubyを使って仕事しています。最近も色々やっているお(^ω^ = ^ω^)

AngularJS 1.4を試してみた

AngularJS Hackathon

ドットインストールで試してみた。

dotinstall.com

で、まだまだ全然わからん。。。

とりあえず色々試してみたことをメモ。

model(?)名にハイフン使っちゃダメ

<p>テスト:
  <input type="text" ng-model="test-model">
</p>
<p>結果:{{test-model}}</p>
<input type="text" ng-model="test-model" class="ng-pristine ng-untouched ng-valid">

エラーの見方がわからん。。。

とりあえずハイフン使っちゃダメね。

html文字列をviewに流しこむ

sanitizeの処理を持つjsをロード

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.5/angular-sanitize.min.js"></script>

js側で、依存設定を追記

app.controller('app', ['ngSanitize']);

view側で、ng-bind-htmlを使って、表示する箇所を追記。

<p ng-bind-html="user.response"></p>

これで、php(サーバーサイド)でvar_dump()で返す値をhtml表示することができる。

imgタグにパスを流し込みたいときは、src属性ではなくng-srcを使う

最初の{{}}を置換する際に404が発生する。ng-srcを使えばおk。

<!-- <img src="{{user.image}}"> これはNG -->
<img ng-src="{{user.image}}"> <!-- これはおk -->

ng-clickする関数に引数を与える

<button class="btn btn-primary" ng-click="addFunction()">

まぁこんな感じで、ng-clickを使って関数を紐付けます。

こいつに引数を渡したい時。

<button class="btn btn-primary" ng-click="addFunction({{id}})">

みたいに渡せそうですが、これはエラーです。どうやらng-***の中に書いている値には{{}}を使わなくても良いとのこと。

<button class="btn btn-primary" ng-click="addFunction(id)">

これでおk

目標ログ日記13

うーん! また悩みが増えた!

運動

スクワットは50回/日へ変更!

合わせて、下記は週に2〜4回ぼちぼち実行中。

  • ジョギング 3km
  • 腹筋ローラー 5回
  • 腹筋 10回

を、追加しました。

で、最近会社の社員に「太りました?」って言われましたとさ(´;ω;`)ブワッ

読書

  • 「嫌われる勇気」を読んでいます。あともうちょい。
  • CODE COMPLETE 上
    • やっぱり理解するのに手こずっています。全然中身が入ってこないぞ。。。

IT

  • CakePHP...というより、コードの再利用、品質、読みやすさを一層意識してコーディングしているつもりだが、自信が全然湧いてこない。。。
    • そもそもレビューしてくれる人もいないので、あたりまえっちゃーあたりまえだけど。

英語

とりま、Hacker NewsをのRSSをSlackに飛ばすように設定してみた。全部英語(当たり前だけど)

なんか文章でも感じをつかめるようにはなってきたけど、相変わらずちゃんと理解できている感じがしない。単語覚えようかね。

今週(今月)良かったこと

  • なんかこう、お客さんの要望の実現。に、ついては時間かかれど、できるようにはなってきたが、いかんせんスピードが足りない。ってことに気づいた。

今週(今月)の反省

  • コーディングのスピードがあがらない原因(と思っていること)をざっとあげる。
    • そもそも全体像とプログラミングの進め方がわかっていない。よって、コーディングが遅い。
    • (たぶんだけど)デザパタなどの知識も不足しているため、DRYがうまくいっていない。よって、コーディングが遅い。

今後

じゃあその課題について、どう動けば良いか。

  • そもそも全体像とプログラミングの進め方がわかっていない。よって、コーディングが遅い。
    • 単純に経験不足だと思うので、引き続き仕事。プライベートにて勉強と手を動かしていく。
  • (たぶんだけど)デザパタなどの知識も不足しているため、DRYがうまくいっていない。よって、コーディングが遅い。
    • だいたい同僚とか上司からレビューしてもらって、共有してもらうことが多いと思うが、いかんせん一人なことが多いので、そういう場を作る。

いまさらCakePHP2.xを使ったここ2ヶ月のことをメモする

CakePHP Pancakes

ここ2ヶ月、CakePHP2.xを久々に触り、立ち上げたプロジェクトで学んだことをメモする。

本当はCakePHP3.xを触りたかったけどね。

ここ最近のCakePHP2.xを使った俺のまとめ

Object志向って何? おいしいの? という、プログラマwが書いた内容です。やさしい気持ちでご覧になって頂けますと幸いです。(厳しいお言葉でも構いませんが、アドバイスほしい)

今回使ったバージョン

  • CakePHP2.8

composer & bake を使ったCakePHPのinstall

公式ドキュメントにも用意されているので、詳しくは書きませんが、composer & bakeを使ったほうが便利と思います。

ただ、composerがインストールされていないWindowsユーザーに仕事ふるのはちょっと面倒だった。...

応用インストール

$ composer install

# installが終わったら、bakeを使ってCakePHPの準備
$ ls -d Vendor
Vendor

# 任意のプロジェクトディレクトリを作成して、bakeで指定する
$ mkdir app
$ ./Vendor/bin/cake bake project app/

複数のログイン・認証のシステム

CakePHPで、認証が必要な要件を満たすときは、AuthComponentを使うと思います。

しかし、ECサイトのように、下記の要件があると、満たすことが去年までできませんでした。

例)ECサイト

  • 管理者ユーザー(商品情報の管理、注文の管理、サイト管理)
  • サイト登録ユーザー(認証必須、注文、過去の注文履歴閲覧、自分の情報を登録・編集)
  • ゲストユーザー(注文不可今はできるサイトがほとんどだけど、サイト閲覧のみ)

調べてみると「できる」と言うサイトは少なくともありました。

sugi511.hatenablog.com

これまでは面倒だったので、管理用と顧客用としてアプリケーションを分けていましたが、今回から相互で使う処理が多く、メンテナンスがすごく面倒になったので、一緒のアプリケーションで複数ログインを実装することになりました。

prefixルーティングという機能を使うと実装できました。

ルーティング

この機能は、その名と通り設定したルーティングにマッチすると接頭辞(prefix)を付けてくれます。

例えば/admin/items/edit/5のようなURLにアクセスしたなら、フレームワークではItemsController.phpadmin_edit($id = null)というアクションへアクセスします。

設定する場合は、app/Config/core.phpapp/Config/routes.phpへ設定が必要です。

  • app/Config/core.php
<?php
...
// 142行目ぐらい
  Configure::write('Routing.prefixes', array('admin'));
  • app/Config/routes.php
<?php
...
// 任意の行で
Router::connect('/admin', array('controller' => 'indexes', 'action' => 'index', 'admin' => true));

上記設定と、AuthCompoentを組み合わせます。

要は、/admin/がついてるかついていないかで、管理者ユーザー、サイト登録ユーザーを分ければ良い。

これをわける処理はapp/Controller/AppController.phpに実装します。

  • app/Controller/AppController.php
<?php
...
class AppController extends Controller
{
...
  // 通常はUserモデルを使った認証にする
  public $components = [
    'Auth' => [
      'authenticate' => [
        'userModel' => 'User',
        'fields' => [
            'username' => 'email'
        ]
      ]
    ]
  ];
...
  // beforeFilter()でprefixを確認して、adminだったらAuthComponentを管理者用に上書きすれば良い
  public function beforeFilter()
  {
    if (isset($this->params['prefix']) && $this->params['prefix'] === 'admin') {
      $this->layout = 'admin';
      $this->_setAdminAuthParameter();
    }
    parent::beforeFilter();
  }

  private function _setAdminAuthParameter() {
    // 管理者ユーザーの場合はAdminUserモデルで認証する
    $this->Auth->authenticate = [
      'Form' => [
        'userModel' => 'AdminUser',
        'passwordHasher' => 'Blowfish',
        'fields' => [
          'username' => 'email'
        ]
      ]
    ];
    // セッションのキーも変更する(通常と同じにすると色々変な動きが)
    AuthComponent::$sessionKey = 'Auth.Owner';
  }
...
}

こんな感じでよし。

あとは、prefixルーティングを使ったControllerのアクション、Viewのテンプレートファイル名の接頭辞にadminを入れ忘れなければおk。

ディレクトリ階層を別ける

クラスを大きくし過ぎると、テストやらデバッグやら面倒になってくるので、できるだけわけたほうが良い。

さらに、先ほどのprefixルーティングを使っていると、管理ユーザー、ログインユーザーがそれぞれアクセスするクラスをディレクトリ階層でわけて、ぱっと見てすぐ判断できるようにしたい。要はメンテナンスのし易い構造にしたい。

そこで、それぞれわけるためにapp/Config/bootstrap.phpへ追記する。

  • app/Config/bootstrap.php
<?php
...
// 43行目ぐらいに追記すればいいかも
App::build(array(
    'Controller' => array(
        ROOT . DS . APP_DIR . DS . 'Controller' . DS,
        ROOT . DS . APP_DIR . DS . 'Controller' . DS . 'Admin' . DS,
    ),
    'Model' => array(
        ROOT . DS . APP_DIR . DS . 'Model' . DS,
        ROOT . DS . APP_DIR . DS . 'Model' . DS . 'Table' . DS,                   // あとで説明
        ROOT . DS . APP_DIR . DS . 'Model' . DS . 'Action' . DS . 'Front' . DS,   // ログインユーザー用ActionModel
        ROOT . DS . APP_DIR . DS . 'Model' . DS . 'Action' . DS . 'Admin' . DS,   // 管理ユーザー用ActionModel
    ),
    'View' => array(
        ROOT . DS . APP_DIR . DS . 'View' . DS,
        ROOT . DS . APP_DIR . DS . 'View' . DS . 'Admin' . DS,
    ),
));

こんな感じで分けると良い。

気をつけてほしい点として、ディレクトリをわけたから、同じクラス名は使えないということ。これはちょっと面倒だし、複数人で開発しているといつか事件になりそう。namespaceとか使えればいいけど、まだ試していません。

MVCそれぞれでアクセスできる共通クラスの用意

今回のプロジェクトでは、認証しているか認証していないか、認証しているユーザーは管理者か、ログインユーザーかなどなど、MVCどの場面においても確認したい要件があった。

MVCにはそれぞれ共通処理を書くところがあり、MはBehavior、CはCompoent、VはHelperという考え方(?)が用意されている。

しかし、今回の要件でいう認証については、全体に向けて共通化してもらわないと困ることが多かった。例えば、Componentで用意したメソッドをViewで使いたい。Modelで使っていた処理をHelperに書き足したなどなど、メンテの面でも結構面倒なことが多々あった。

なので、どっからでも使える共通クラスをVendorディレクトリに用意することにした。(Pluginとして用意するのもありだと思ったが、別にプラグインとして使うものでもなかったので)

  • app/Vendor/util/AuthUtility.php
<?php
namespace Util;
\App::uses('AuthComponent', 'Controller/Component');

class AuthUtility
{
  private $auth;

  public function __construct()
  {
    $collection = new \ComponentCollection();
    $this->auth = new \AuthComponent($collection);
  }

  /**
   * @return array|null
   */
  public function getLoginUser()
  {
    return $this->auth->user();
  }
}

このクラスをApp::import()を使って、使えるようにする。また、オブジェクト化する際にnamespaceを指定して使うのも忘れずに。

<?php
App::import('Vendor', 'util/AuthUtility');

...
  public function testModel()
  {
    $auth = new Util\AuthUtility();
    return $auth->getLoginUser();
  }

これで共通化することができる。

ほんの最近困っていることは、呼び出すたびにオブジェクト化しているので、今後認証共通処理が追加していく毎に重くなると予想している。

なので、MVC呼び出しの初回の処理で一回オブジェクト化して、それを使いまわせるようなEntitiyクラスが作れないかなと考えています。

Controllerとのつきあい方

油断しているとどんどんコードが増えてくるので、なるべく小さく書くように意識しています。

よく太らせる原因とその移動場所

ビジネスロジック、findのパラメータ...というかfindもすべて、paginateのパラメータ作成・値のチェック処理などなどすべてModelへぶっこみました。

なるべく、Controllerでしかできないことを意識してコーディングするようにしています。

  • httpメソッドのチェック
  • redirect
  • modelから値をもらって、viewへ渡す

また、リクエストを受けとる処理もControllerの仕事だと思っていましたが、Modelでも取得できるようです。

<?php

class HogeHoge extends AppModel
{
...
  public function getRequest()
  {
    // これでrequestObjectが取得できる
    $requestObject = Router::getRequest();
  }
}

こいつができれば、リクエストの引数チェックもすべてModelに持っていけるようになりました。

また、Actionについて。Actionを1つにして、引数で削除処理なのか、更新処理なのかを処理する方法を1度とったことがあります。

<?php
...
  public function action()
  {
    if ($this->request->is('post')) {
      $requestData = $this->request->data;
      if ($requestData['request'] === 'edit') {
        // edit
        $this->_edit($requestData);
      } else if ($requestData['reqest'] === 'delete') {
        // delete
        $this->_delete($requestData);
      }
      ...
    }
  }

これはすぐにやめました。最初viewも使いまわせて少なくなるし便利かなーと考えた方法でしたが、運用のフェーズになるとすごくめんどくさくなりました。

処理を追加したいとか、削除の処理だけこうしたいとかなったときに、すごく触りづらいコードだとこと。

bakeもそうですが、addアクションとeditアクションをそれぞれ生成するので、アクションメソッドもなるべく別けたほうが良いです。

ただし、PaginateComponentを使うページだけは、index()search_index()と別けることもありました。が、これに関してはわけないようが良いです。

PaginateComponentでは、ページネーションを使う上で、ソートしたり、検索ができるようにしたいなど要件が追加されることが多いので、同じメソッドで管理したほうが、コードもまとめやすいし組みやすかったです。

Componentの利用

だんだん使わなくなりました。。。

最初は、ファイルアップロードの処理とか、CakeEmailを使う前の準備Componentなどで利用していましたが、ほとんどModelとBehaviorに移動してしまいました。

もしかしたらあんまり使わないかもしれません。。。

Viewとのつきあいかた

Viewにロジックを書かないで! というのを意識しました。とわいえ、条件でViewを変えたいなど絶対でてきますので、そういう場合はHelperを使ったほうが良いです。テストもし易くなります。

さらに、デザインが汎用的で綺麗に用意されているのであれば、Elementがすごく力を発揮してくれます。ほとんどviewのblockがつかいまわせるようなhtml構造であれば、迷わずelement化しましょう。商品のリストとか、ログイン後にでてくるヘッダーとか。

ViewからModelを利用

正確にはHelperの中で、Modelを利用しました。

とあるModelのステータスを確認するだけで、「ControllerからModelにアクセスして、データを確認して、その値をViewに渡す」って流れがすごく面倒だったので、Helperからアクセスすればフローが短くなると思ってそうするようになりました。

<?php
...
  public function myHelper()
  {
    $hoge = ClassRegistry::init('Hoge');
  }
...

ちなみに、更新とか削除、データの取得はHelperからしません。あくまでデータの参照だけです。

Modelとのつきあい方

ほとんどの処理をModelに詰め込みました。なので、すげぇーモデルのクラスが多いです。

アソシーエーションは使わない

完全に使わないわけではないですが、常時つけていると外す処理などが結構面倒です。

なので、常はどこも接続しない状況にして、3つのテーブルを全部更新とかの保存処理とか面倒なところだけ、bindModelを使ってアソシエーションを使う。ってやりかたがいいと思った。

保存、更新、削除処理はtry ~ catchで囲む

想定していないデータが渡ったときに、よくエラーが発生してお客さんびっくり!ってなります。それを回避するためにtry ~ catchを使いましょう。

また、catchの中で、ログを吐いたり、ロールバックできる処理を書けば、安全なシステムを作ることができる。

共通データ処理、共通ビジネスロジック、ActionModel

処理・目的 移動Class
save、update、deleteなどの処理の更新処理 ~/app/Model/Table/Model.php
find()のパラメータと処理 ~/app/Model/Table/Model.php
アソシエーション、bindModel() ~/app/Model/Table/Model.php
ビジネスロジック ~/app/Model/Behavior/Behavior.php
ControllerのActionに紐づく処理(リクエストデータの引数チェックなど) ~/app/Model/Action/ActionModel.php

こんな感じ。これまでは全部~/app/Model/ディレクトリに全部ぶっこんでいましたが、さすがに見づらかったので、~/app/Config/bootstrap.phpで構造を変更できるようにしました。

~/app/Model/Table/に入るクラスは、これまでCakePHPで用意するModelクラスが入ります。

~/app/Model/Action/は、ControllerのActionメソッド毎に作成します。(用意する必要がないActionの場合、Modelは用意しません)主に、リクエストパラメータの存在チェックや、保存までの受け渡し処理。本来ならController側で書かないといけないことをこのActionクラスに持たせるようします。そうすることで、Controller側で書くことが少なくなり、テストがしやすくなります。

JavaScript、またはJavaScriptフレームワークPHPの友情

(なんかうまいタイトルを思いつきませんでした)

viewにこだわる案件が増えてきました。となると、活躍するのがjavascriptでしょう。

例えば、注文画面で個数を変えたら自動で金額が変わったり、在庫以上に個数を入力したらアラート発生したり。などなど。

そういう要件が増えてくると、jsのコードも多くなるはず。

ここで(個人的に)問題になるのが、jsとphpでのコードの重複です。

最初はPHPでviewを用意するのですが、あとからjsで動きをつけているって感じの実装をしていきます。

jqueryでよくあるのが、ユーザーが選択してviewが増えていくような実装をしていくようになると、phpで用意するviewとjsで用意するviewが被ってしまい、メンテナンスするときに両方手を入れないといけないため、すごく面倒なことが多かったです。

そもそもの要件定義などで汲み取るのが下手くそだったってことも原因のひとつだとは思いますが、お客さんのほとんどはリテラシーもなく、実際に使わないとイメージできない人が多数なので、そこまで完璧な要件汲み取りはほとんど不可能だと思います。

そこで、最近考えついたのはPHPAPI化です。

操作が多い管理画面などは、ページ遷移しない、作業のステップが少ないほうが喜ばれることが多かったので、viewはすべてjsで管理するようにします。

PHP側、CakePHPはデータを引っ張ってきて、フィルターをかけるなりデータを加工し、jsonで返すだけのAPI化にすれば、役割としてはっきりするようになって、メンテしやすくなると考えます。

実際、それが嫌になって、一部ページだけそれを試してみたところ。すごくすっきりしました。(そのあとjQueryがだるくなって、AngularJSに変えたのは別の話)

クライアント側が担う責務は、これからどんどん増してくると思うので、こんな感じの組み方にしていこうかなと思います。

PHPが本気でテンプレートエンジンをやめるときが来るかも。

マイグレーションの利用

ここを参考にコマンドを実行していました。

migrations/Generate-Migrations-Without-DB-Interaction.md at master · CakeDC/migrations · GitHub

ただ、マイグレーションのコマンドだけでは実現できないことが多かったりするので(俺が知らないだけかもしれませんが)、そういうときは、マイグレーションファイルだけ作成して、before & afterのメソッドに処理を書いていました。

【alter ... modify ...】binaryって指定すると、blobにしかならないカラムをmediumblobに変更したい

画像とか保存するときに、blobでは小さい場合がほとんどです。なので、mediumblobにしようとしたら、コマンドでは認識させることができなかった。

マイグレーションを実行する前後で処理されるcallbackが用意されているので、そこであとから書き換えるsqlを実行するようにしました。

また、callbackには$directionというパラメータが渡されます。マイグレーションのバージョンが前に進む(up)のか、後ろに戻る(down)のかを判断するときに使います。

<?php
...
  'image' => array('type' => 'binary', 'null' => false, 'default' => null),
...
  public function after($direction) {
    if ($direction === 'up') {
      $topSliderManager = ClassRegistry::init('TopSliderManager');
      $topSliderManager->query('alter table top_slider_managers modify image mediumblob not null');
    }
        return true;
  }

【alter ... add ... after xxx】カラムを追加するときに、特定のフィールドを指定してその後ろに追加

これもコマンドでは用意できないので。

マイグレーションファイルを作成後、追加するカラムの連想配列に追記する。

keyをafter、valueをフィールド名にする。

<?php
...
  'image' => array('type' => 'binary', 'null' => false, 'default' => null, 'after' => 'image_name'),
...

cakephp3.xでは使いやすくなっていることを祈る (人∀・)タノム

感想

CakePHP2.xの利用は恐らくこれが最後になるかなと思います。

次回は3で攻める予定です。どんだけ変わっているかなー。。。。。

【MySQL】varchar型のカラムをinteger側で検索したいとき

商品のテーブルがあり、その中で登録しているカラム「商品の高さ」を"0cm〜30cmまで"みたいな検索を実装したかった。

普通に考えたらこんな感じ。

mysql> select * from items where height between '0' and '30';

しかし、その昔の仕様の際は「cm」と単位が入っていて、カラムの型がvarcharでした。なので、検索ができない事態に陥る。

さて、困った。

でも調べたらすぐわかりました。

castしてあげればおk

mysql> select * from items where cast(height as unsigned) between '0' and '300';

ちなみに、実装したのはCakePHP側だったので、下記のように修正

<?php

...

'conditions' => [
  '(Item.height as unsigned) BETWEEN ? AND ?' => $heightQueryArray // [0, 30] な感じで配列が入っている
]

...

めでたしめでた。。。

と、思ったけど、やっぱりカラムの型を修正しました。こういうのって後回しにしていると後が怖いからね。

$ alter table items modify column height integer;

iOSのjavascriptのeventでfocus + setSelectionRange()が効かない

どうすればいいんだ。。。。。

Javascriptと、いっておいて実はjquery

var inputTextAllSelect = function(e) {
  try {
    // ここでiosで全選択させるように処理
    e.target.setSelectionRange(0, e.target.value.length);
  } catch (exc) {
    // PCでは、input[type="number"]とかだとエラーになるので、ここは便利な関数を使う
    $(e.target).select();
  }
};

$('input').on({
  focus: function(e) {
    inputTextAllSelect(e);
  }
});

これだとうまくいかない

focusで受け取ってはいて、一度は全選択されるんだけど、続けてよくわからない処理に邪魔されて全選択が解除される。

この動きを0.05秒ぐらいの速さで処理されるので動いていないように見える。

さて困った。

なんてこともなかった。

var inputTextAllSelect = function(e) {
  try {
    // ここでiosで全選択させるように処理
    e.target.setSelectionRange(0, e.target.value.length);
  } catch (exc) {
    // PCでは、input[type="number"]とかだとエラーになるので、ここは便利な関数を使う
    $(e.target).select();
  }
};

$('input').on({
  click: function(e) {
    inputTextAllSelect(e);
  }
});

eventclickにしたらできた。でも本当はfocusがいいなぁ。。。

謎は解けないけど、仕事は進んだので良しとします。