またどこかでCTOっぽいことやってる人のブログ

フリーランスを経て、またどこかでCTOっぽいことをやってる人が書いてます。何か色々やってます。

気がついたら2017年になっていた。

2017年になっていました。
自分は毎年元旦に「今年はこれを個人的に作ろう」という目標ぽいことを立てるタイプなのですが、2016年は目標未達成でした。
ですので「2016年にできなかったアレを2017年にローンチしよう」という目標にすることにしました。
アレというのはチマチマ作ってるアレです。

従事している業務の方はなかなか多忙を極めておりまして、なかなか時間が取れないのが悩み。
なのですが、業務は業務、これはこれという区分けで今年も何か作ってみようと思っています。

プロダクトを作るというのはなかなか大変で、いわゆるスタートアップですと「えいやっ!」ととりあえず作ってローンチ。
それを頑張って拡大させるというところもあると思います。弊社もどちらかというとそうです。
しかしながら、やっぱりサービスが大きくなっていくと「あの時、ちゃんと考えて作ればよかったな」と思うことが多々あります。
ローンチした時はよかったんです。
あの時はそれで正解だった、はずなんです。
が、気がついたら「やってしまった…」となることが最近増えてきました。

サービスが拡大している証拠でもあるので、そういう観点からだとアリなんですが過去の自分を戒めたい気持ちにもなります。
という経験をして、次に作るサービスであったり機能だったりなんかは「何か将来的にこんな展開になるかもしれないな」という心持ちで作るようになりました。

これを弊社では「エスパースキルを発動させて開発」みたいに呼んでいます。
今年も発動させまくりたいと思います。

CakePHP3でEntityからDate型のデータを日付計算して取得してみた。

日付の比較とか計算とかあまり好きではありません。
いつもグーグル先生にお申し立てをしてしまいます。
そんな自分が今回やったことはタイトルの通り。

■やりたかったこと
 Date型のデータの1日前のデータを取得したかった。

■やったこと
 Entityで計算した結果を返すようにした。

Entityのgetterでやりたかったんです。
実際に使う時はViewの方だったのですが、Viewにごちゃごちゃ書きたくなかったので。
結局、こんな風にしてみました。

<?php
 protected function _getPastSampleDate() {
    
  if (isset($this->_properties['sample_date'])) {
    return date('Y-m-d', strtotime('-1 day', strtotime($this->_properties['sample_date']->format('Y-m-d'))));
    } else {
        return '';
    }
 }


strtotime辺りはよくある日付操作と同じです。
ただフォーマットを掛けて、1日分マイナスしているだけです。
使う時は普通のEntityと同じです。

<?php
 if ($sampleObject->past_sample_date > date('Y-m-d')) {
   // 何か処理 
 }

これでやりたいことができました。


そう言えば。
CakePHP3で開発する時、View側でdatetimepickerを使うと日付表示フォーマットが様子おかしくなります。
犯人はfrozentimeなのですが、これを回避したい時はconfig\bootstrap.phpに以下を書くといいです。

 // 日付型フォーマットのデフォルト出力設定
 \Cake\I18n\Time::setToStringFormat('yyyy-MM-dd HH:mm:ss');
 \Cake\I18n\FrozenTime::setToStringFormat('yyyy-MM-dd HH:mm:ss');
 \Cake\I18n\Date::setToStringFormat('yyyy-MM-dd');
 \Cake\I18n\FrozenDate::setToStringFormat('yyyy-MM-dd');


\Cake\I18n~の部分はuseしてしまっても構いません。
今回は便宜的にベタっと書いておきました。

公式の方はJson出力のサンプルが書いてありますが、同じような感じです。
http://book.cakephp.org/3.0/ja/core-libraries/time.html#id3book.cakephp.org

CakePHP3で入力された複数のカラム同士を比較するバリデーションを作ってみた。

今回のネタは合っているのかどうかよくわかっていません。
やりたかったことは大したことではなく

  • 入力されたFrom日付とTo日付の比較

です。
よくあるアレです。入力されたFrom日付がTo日付より未来日の場合はダメというやつです。
最初はvalidationDefaultをごにょごにょやっていたのですが、どうも上手くできず。
もしかしたらできないのかもしれませんが、その辺調べていません。
ですので、buildRulesの方に実装してみました。

以前書いたように、CakePHP3のバリデーションには2種類あります。
ひとつはPOSTデータを送信する際に処理される「validationDefault」、もうひとつはsaveメソッド実行時に処理される「buildRules」です。
ごにょごにょと触った感じ、validationDefaultの方はPOSTデータのカラムをひとつずつ、何らかのバリデーションを実行しているように思えました。
CakePHP2の頃は入力カラム同士でバリデーションできたような気がするのですが、気のせいだったかもしれません。

前置きはともかく、こんな処理を作って実装しました。
昔の自分が書いたコードを雑に修正しただけなんですが。

<?php
 public function buildRules(RulesChecker $rules) {
        
    // 開始日時と終了日時の入力範囲チェック
    $rules->add(
        function ($entity, $options) {
            if ($entity->to_time < $entity->from_time) {
                return false;
            } else {
                // 未入力の場合はtrueで返す
                return true;
            }
        }, [
            'errorField' => 'start_datetime', 
            'message' => '開始日時に設定された日付が、終了日時より未来日として設定されています。'
        ]
    );

    // 処理結果を返す
    return $rules;
 }

この処理はTableに書いてあるのですが、$entityというのはEntity配下に作成するであろう対象テーブルのエンティティです。
エンティティから比較したいカラムを抜いてきて、それを単純に比較しているだけです。
あとはCakePHP3のバリデーションルールに沿って、明示的にtrue / falseを返す。これだけです。

今回は日付のバリデーションなので、Controller側に直接書いたり入力時点でJavaScriptで判定したりとかでもいいと思います。
なんですが、バリデーションはバリデーションとしてまとまってた方がいいかなと。

これもまた個人的な好みです。

WindowsのXAMPPにインストールしたCakePHPからシェルを実行してみた。

とは言っても、ローカルテストにしか使い道がない気がします。
本番運用でWindowsサーバにCakePHP3を載せるのであれば話は別ですが。

CakePHP3になってシェルの配置が変わっています。

CakePHP3インストールディレクトリ\src\Shell

この辺にあります。
インストールすると「ConsoleShell.php」なるものがあるので、とりあえずこれを実行してみます。

Shellの実行ディレクトリは以下です。
あ。タイトルにあるようにXAMPPの場合です。
自分の環境はhtdocs配下にCakePHP3を入れているので

C:\xampp\htdocs\プロジェクト名\bin

ここにありました。

あとはコマンドプロンプトを開いて、上記のShell実行ディレクトリに移動して、CakePHP2の時のように実行すればOKです。

cd /d C:\xampp\htdocs\プロジェクト名\bin
C:\xampp\htdocs\プロジェクト名\bin>cake Console

ローカルテスト用ということで。

CakePHP3でDBに格納した画像データを表示してみた。

2017/09/04 追記
すっかり書いた気でいた画像データ登録記事を追加しました。
本記事と合わせて読んで頂くと、それっぽくできると思います。

tsuralabo.hatenablog.com



画像データはDBにブチ込むタイプです。
サーバにアップロードする方法もありますが、どちらもメリット、デメリットあります。
メリット、デメリットに関してはグーグル先生が詳しいと思います。
案件に応じて柔軟に対応すればいいんじゃないかなと。

さて、いきなり端折ってしまいますが、image_filesというテーブルに画像データが入っていたとします。
いわゆるBLOB型のカラムですね。
テーブル構成はこんな感じで作りました。

CREATE TABLE `image_files` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content_type` varchar(64) NOT NULL COMMENT 'コンテンツタイプ',
  `image_data` mediumblob NOT NULL COMMENT '画像バイナリデータ',
  `created` datetime DEFAULT NULL COMMENT 'データ作成日',
  `modified` datetime DEFAULT NULL COMMENT 'データ更新日',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='画像格納テーブル'

ここにブチ込んだ画像バイナリデータをController経由で表示します。
Helperでもいいと思いますが、あんまりHelperを使うのが好きではないのでControllerで。

やりたいことはこれだけです。

・プライマリキーであるimage_files.idを指定する。
・image_files.idに合致するバイナリデータのimage_files.image_dataを取得する。
・画面上に表示する。

なんか簡単っぽいです。
というわけで表示用のControllerを書いてみます。

<?php
  namespace App\Controller;
  use Cake\Event\Event;
  use Cake\ORM\TableRegistry;
  
/**
 * ImageFilesController
 */
class ImageFilesController extends AppController {
  
  public $name = 'ImageFiles';
  
  public function beforeFilter(Event $event) {
    parent::beforeFilter($event);
  }
  
  /**
   * 画像データ取得処理
   * IDに合致する画像データを取得して表示
   * @param type $imageId
   * @throws NotFoundException
   */
  public function content($imageId) {
    
    // Viewをレンダリングしない
    $this->autoRender = false;
    
    // image_filesデータをまるっと取得する
    $imageFile = TableRegistry::get('ImageFiles');
    
    // IDに紐付く画像データを取得
    $fileData = $imageFile->get($imageId);
    
    if (empty($fileData)) {
      // 一応エラー処理
      throw new NotFoundException();
    }
    
    // 画像表示
    $this->response->type($fileData->content_type);
    $this->response->body(stream_get_contents($fileData->image_data));
  }
}

ポイントは特にありません。
強いて言えば、レンダリングしない部分だけじゃないかなと。
あとはよくある画像表示処理です。

このコントローラをView側で呼んであげれば画像が表示されると思います。
View側も一応記載しておきます。

<?php
  <img 
     src="<?php echo $this->Url->build(["controller" => "ImageFiles", "action" => "content", 12345]); ?>" 
     class="img-rounded img-responsive" <!-- ここのClass指定はBootstrap3用 -->
     alt="画像12345です" 
  />

上の例だとimage_files.id = 12345の画像を指定しています。
altは適当に記載していますが、実際はちゃんと入れないとグーグル先生がクローリングしてきた時に叱られます。

出来上がったソースを見ると、CakePHP2の頃とほとんど変わりませんでした。
が、とりあえず画像は表示できたのでよしとします。

CakePHP3でbodyタグ閉じ直前にJavaScriptを読み込むようにしてみた。

datatablesを多用する自分はJQueryやらdatatablesやらのモジュールはheadタグの中で読み込みます。
bodyタグ閉じの直前がセオリーだというのはかろうじて知っているのですが、datatablesモジュール読み込みをbodyタグ閉じ直前に配置すると、デフォルトソート条件なんかが効かないのです。例えばpageLengthとか、orderとかああいうやつです。
いや、自分が書くと効かないだけかもしれませんが、とにかく効かないのです。


で。
datatablesはJQueryやらのモジュールを読み込んだ後じゃないと有効になりませんので、その辺はまとめてheadタグの中にブチ込みます。
良い子の皆様は真似しない方がいいかと思いますが、どうしても動かない時は試しにやってみるといいかもしれません。
とりあえず動かすって大事です。


とは言え、使うJavaScriptを全部headの中で読み込むのは何かモニョっとします。
ですのでView単位で使うJavaScriptはbodyタグ閉じ直前で読み込みたい。
さて、どうしたものか。

普通の方々はCakePHPでプロジェクトを作る方ってレイアウトファイルはどんな風にしてるんでしょう。
自分は至ってシンプルな書き方をしています。

<!-- Layouts/default.ctpだと思って下さい -->
<?php
<!DOCTYPE html>
<html>
  <head>
    <?php echo $this->Html->charset(); ?>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,user-scalable=no">
    <title>
      <?php echo isset($viewTitle) ? $viewTitle : ''; ?>
    </title>
    <!-- metaタグとかが必要ならこの辺りで -->
    <?php echo "CSS読み込んでfetch" ?>
    <?php echo "仕方ないのでJavaScript読み込んでfetch"; ?>
  </head>
  <body>
    <div id="wrap">
      <!-- Elementでヘッダーっぽいのを読み込み -->
      <?php echo $this->Element('Layout/header'); ?>
      <div class="row">
        <div id="contents" class="container-fluid">
          <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
            <div id="loading">
               <!-- ローディング画像 -->
              <?php echo $this->Html->image('loader.gif'); ?>
            </div>
            <!-- Viewの内容 -->
            <?php echo $this->fetch('content'); ?>
          </div>
        </div>
      </div>
    </div>
    <div id="footer">
      <!-- Elementでフッターっぽいのを読み込み -->
      <?php echo $this->Element('Layout/footer'); ?>
    </div>
    <!-- これを予め書いておく -->
    <?php echo $this->fetch('scriptBottom'); ?>
    <script>
      // ローディング画像ごにょごにょ
      $(function() {
        $("#loading").fadeOut(800);
      });
    </script>
  </body>
</html>

Elementで色々分けるのが好きなのです。
それはどうでもいいのですが、「scriptBottom」というfetchを自分はレイアウトファイルに書いてしまいます。
他の上手いやり方あるのかもしれませんが、まぁこの際いいです。


それでViewの方にはこんな風に書きます。

<?php
 <?php echo $this->Html->script('search', ['block' => 'scriptBottom']); ?>
 <!-- 以下はごにょごにょとViewを構成する何かのコード -->


と書くと、上記の例で言うと「js/search.js」がレイアウトファイルで指定したbodyタグ閉じ直前で読み込まれます。
CakePHP2であったinlineみたいなのはなくなってしまっている模様です。よく調べてませんが。
とにかく、HTMLソースを見ると上のレイアウトで言うところの、ローディング画像ごにょごにょ部分の上にsearch.jsが読み込まれていると思います。


今回もまた雑なコードです。

CakePHP3をWindows10にインストールしてみた。

先日、昔中古で買ったWindows7 Pro 32bitのノートPCを滑り込みでWindows10にしてみました。
特に問題もなくアップデートできたのですが、調べてみたところ32bitのWindows10に対して、64bitのWindows10でクリーンインストールするとWindows10 + 64bitになるとか。
試しにやってみたら本当に64bitになりました。Microsoft曰く、ライセンス的にも問題ないそうです。

インストール時につまづいたところは特になかったのですが、クリーンインストールなのでHDDを削除する辺りは少し躊躇しました。
結局はクリーンインストール実行したのですが、元々ノートに入っていたリカバリ領域まで電子の海にドナドナされました。
でも、万が一の場合はまたWindows10をクリーンインストールすればいいんじゃないの? などと楽観的に考えております。
終わったことは仕方がないのです。
ちなみにドライバ等も特に問題なかったです。メモリ増設しようかななどと企んでおります。

さて。
上記のようにノリで出来上がったWindows10なのですが、特に用途がありません。
用途がない時はとりあえず開発環境をインストールすれば暇潰しにはなるかな? と思い、CakePHP3の開発環境を構築してみました。
今回入れたモノは以下の通りです。

・CakePHP3
・XAMPP
・Composer
・SourceTree
・NetBeans8.1
MySQL Workbench

ちゃんと上手くインストールできるかなと身構えていたものの、全く何も起こらずに開発環境ができてしまいました。
昔の自分が書いた記事を見ながらインストールしたのですが、ちゃんと手順通り書いてるじゃんと感心しました。

と、このように全く何も事件発生せずにインストールできてしまったので、記事にするほどではないのですが一応書いてみました。
ちなみにバージョンは「調べるの面倒だからとりあえず最新をブチ込む」派ですので、2016年8月時点での最新版をそれぞれインストールしました。

CakePHP3開発のIDEとしてNetBeansを使うのであれば、バージョンは8.1以上がいいと思います(多分)