SiG Staff Blog

福井と金沢にある株式会社SIG 総合研究所で働きたい方、ご連絡ください。

PHPにDIは必要か という話を聞いて。

PHPの現場 にて

最近PodCastにはまって、テック系の物をいくつか聞いてるんです。 フロント系が多いイメージのPodCastですが、PHPの話をしてくれる珍しいチャンネルがあって、 放送されたら必ず聞いてるんですが、そこでDIコンテナってPHPにいる?という話がありました。

Zendで更新が停滞しているオレオレフレームワーク作っていて、ふむふむそうだよねって思ったのでちょっと紹介。

php-genba.shin1x1.com

podcsstの概略

  • 便利は便利だから、DIの使いどころを間違わないようにしよう。
  • 今後、DIコンテナの有無でフレームワークの選択を分ける感じになるんでは。

うろ覚えなんだけどこんな感じで、結論的には、

DIコンテナは便利なんだけど、ちょっとPHPを使うにあたって大げさすぎるシーンが結構ある。

こんな感じ?だったと思います。

個人的な感想

DIは便利なのであったほうが良いかと思う。 ただ、あったほうが良いシチュエーションっていうのが結構おっきめのプロジェクトであることが多いかと思ってて、 そういうプロジェクトの時はF/W選んでポチポチ準備していくのはありかもしれないが、 そうじゃない、数か月だけ稼働するとかの場合はDIみたいな仕組みは無くて、突貫で作れればいいやん。

PHPの現場では、「それぞれのシチュエーションに合わせてF/Wを選ぶ感じになるじゃない?」という事だったけど、 DIコンテナありのF/Wにオレオレカスタマイズを入れて、

  • 短期集中の時は細かい設定せずに身を任せる。(どうせそんなに大量の機能が無いだろうし)
  • 長いプロジェクトの時は、設定とかテスト設定とかゴリゴリやる。

って感じにどっちでも転がれるようにF/Wの設定済ませて有る物を準備すればいいんかなぁと思った。

ちょこちょこ作ってるオレオレフレームワーク、Zendで作っているけど、Laravelをしてみたいから移行してみようかな。

兎にも角にも、次の収録が放送されるのが楽しみ。

指示通りSkeletonを使ってサンプルサイト作ったけど、なんかワーニング出るやん!

Zend Framework2で急にワーニングがでた!何もしてないのになんで!?

指示通り作ったのにワーニングがでてる!

Zend Framework2に書いてある通り、 Skeltonを入れて、 サービスの登録とかを諸々したら画面が出る(出てた)んだけど

ある日、

Deprecated: You are retrieving the service locator from within the class Application\Controller\IndexController. Please be aware that ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. You will need to update your class to accept all dependencies at creation, either via constructor arguments or setters, and use a factory to perform the injections.

って文字が画面の上に出てる。 前までちゃんと動いていたのになんで???

Zendは3.0に向けて動いてるらしい

3.0からはZendもDIっぽくやっていくよ!って事らしく。

ServiceとかでServiceLocatorを使う時に、$this->serviceLocator の様に呼びださずに Module.phpのgetServiceConfigメソッドでServiceのクラス変数に値を入れるようにしなさい。 って事らしい。

$this->serviceLocatorみたいに呼びだせないようにするからね!って事だそうで。

いきなりDeprecated出るようなアップデートしないでYO

で、AwareInterfaceってなんぞ?

ControllerとかにLoggerを持っているか、ServiceManagerを表現するインターフェースのようで、

LoggerAwareInterfaceを持ってたらsetLogger()メソッドを持たすことを強要できるので、 Controllerクラスを作成するタイミングで、クラス変数とかに$loggerとかにセットしてあげればloggerが使えるという感じです。

ServiceManagerAwareInterfaceだったら、setServiceManager()メソッドをつけれるので、Controllerにサービスマネージャをセットできます。

という事らしい。

で、やってみた。

各コントローラのクラスにLoggerAwareInterface, ServiceManagerAwareInterfaceをimplementsする。

abstract class BaseController extends AbstractActionController implements LoggerAwareInterface, ServiceManagerAwareInterface
{

    /** 改行をBRに変更する */
    public $nl2br;

    /** ロガー(monolog) */
    protected $log;

    /** サービスマネージャ */
    protected $sm;

    /**
     * loggerの設定。initializerで設定
     */
    public function setLogger(Logger $log) {
        $this->log = $log;
    }

    /**
     * サービスマネージャの設定。initializerで設定
     */
    public function setServiceManager(ServiceManager $serviceManager) {
        $this->sm = $serviceManager;
    }
    /*
     * 諸々の処理
     */

で、初期設定をするためのクラスを設定する。

/**
 * コントローラーの初期設定を行う
 */
class ControllerInitializer implements InitializerInterface
{
    public function initialize($instance, ServiceLocatorInterface $serviceLocator)
    {
        //loggerの設定
        if ($instance instanceof LoggerAwareInterface){
            $instance->setlogger(\Application\Common\Log::createBasicLogger(Setting::APPLICATION_NAME));
        }

        //service managerの設定
        if ($instance instanceof ServiceManagerAwareInterface){
            $instance->setServiceManager($serviceLocator->getServiceLocator());
        }
    }
}

DIコンテナもどきなので、Module.php経由でコントローラを呼びだす設定の所に、 イニシャライズとして、さっき作ったクラスを指定する。

    /*
     * コントローラーの設定をする
     */
    public function getControllerConfig() {
        return array(
            'invokables' => array(
                'Application\Controller\BaseController' => 'Application\Controller\BaseController',
            ),
            'initializers' => array(
                'Application\Initializer\Controller\ControllerInitializer',
            ),
        );
    }

で、便利なん?

LoggerとかSMとかだとあんまり嬉しくないけど、ファイルに保存するクラスなどを

  • ローカル開発なら、ローカルに保存するクラス。
  • STGならS3に保存するクラス

などに差し替える事ができるので、便利ですね。

slack + hubot + Backlog Apiでプロジェクトの予定と実績の差をとる

色々あって便利ですよね、Backlog

Backlogいいね!

これまで、クラウドサービスの使用禁止という世間の流れから逆流した開発をさせられてて、

Redmineでタスク管理をしていたのですが、ようやくBacklogの使用許可がおりて使っております。

ticketも使えるし、wikiもあるし、Gitもあるしいいよね。社外でも見れるし。

Backlogよくないね!

Redmineと違って、サブプロジェクト作れない、チケットが親子までしか作れない、Gitのレビューコメントがちょいと見にくいとかあるんですね。

APIが微妙に使いにくいとか、webhookがちょっと微妙とか・・・。

でも、これらは無くても全然プロジェクト進行できるしいいんですよ。

困るのはサマリー系が全然ない事。予定・実績とかの管理が全然できないんです。

どうしたもんか・・・。

なければ作ればいいって偉い人が言ってるよ

Backlog API課題の情報を取得するのがあるやん。

プロジェクトの課題を全部取ってきて、自分でサマリーしてSlackに通知すればいいんじゃ?

ってことで、herokuにslack連携を済ませてあるhubot がいるので彼の仕事を増やしてあげる事にしました。

欲しいのはソースですよね・・・。

動かし方

必要な物は

  • Backlogのトーク
  • スペースID
  • プロジェクトの情報

です。これらを

  • api_key
  • space_id
  • project_list

にいい感じにセットしてあげてください。

で、botにメンションを付けて

@botName give me 【呼出用のプロジェクト名】

ってすれば動いてくれます。リクエスト投げまくるので結果が返ってくるまで少し時間かかります。

ってことで、ソース

ソース

少し長いです。

  robot.respond /give me[ ]?(.*)/i, (msg) ->

    #Backlogのトークンを設定する。個人設定から取得する
    api_key = "【トークンの設定】"

    #BacklogのスペースIDを設定する。https://[ここの部分].backlog.jp
    space_id = "【スペースIDの設定】"

    #プロジェクトのリストを取得する
    project_list = 
      "【呼出用のプロジェクト名】":
        name: "【画面表示用のプロジェクト名】"
        id: "【backlogのprojectのID】"
        start: 1
        end: 300

    target_project = msg.match[1]
    
    if not (target_project? and target_project of project_list)
      project_names = ""
      for key of project_list
        project_names += key + ", "
      msg.send "どのプロジェクトなの? -> #{project_names}"
      return -1
    
    target_data = project_list[target_project]
    target_name = target_data["name"]
    target_id = target_data["id"]
    target_start = target_data["start"]
    target_end = target_data["end"]

    if not (target_name? and target_id? and target_start? and target_end? and isFinite(target_start) and isFinite(target_end) and target_start < target_end)
      msg.send "プロジェクトの設定値を確認してください。 -> name: #{target_name}, id: #{target_id}, start: #{target_start}, end: #{target_end}"
      return -1

    #小数点以下を切り上げするためのfunction
    floatFormat = ( number, n ) ->
      _pow = Math.pow( 10 , n )
      Math.round( number * _pow ) / _pow

    #Backlog apiを使って課題の集計をするfunction
    # @param bl_project_id プロジェクトID (プロジェクト作成した時に指定する値。課題の前に付くあれ)
    # @param start 対象にするチケットの開始位置
    # @param end 対象にするチケットの終了位置
    call_backlog = (bl_project_id, start, end) ->
      #ループのサイズ(ループをStopさせる用)
      loop_sise = end
      #ループの開始位置
      loop_start = start
      #ループの終了位置
      loop_end = loop_start + loop_sise - 1
      #課題データ
      actual = {};
      #課題データを貯めるリスト
      actual_list = [];
      #作業位置
      finish_cnt = 0

      #プロジェクトIDを取得する
      back_log_url_project = "https://#{space_id}.backlog.jp/api/v2/projects/#{bl_project_id}?apiKey=#{api_key}"

      request = robot.http(back_log_url_project)
                     .get()
      request (err0, res0, body0) ->
        json0 = JSON.parse body0
        project_id = json0["id"]
        
        #課題の数を取得する。
        #課題の取得が非同期なのでうまく止まるようにチケットの最大値を取得する。
        #Promiseを使えればきっとうまくできるはずなんですが。
        back_log_url_count = "https://#{space_id}.backlog.jp/api/v2/issues/count?apiKey=#{api_key}&projectId[]=#{project_id}"

        request = robot.http(back_log_url_count)
                      .get()
        request (err1, res1, body1) ->
          json1 = JSON.parse body1
          
          #設定されている値がチケットの数よりも多いと、複数回サマリー結果が出てしまうので
          #チケットの数までループするように変更する
          ticket_max_size = json1["count"]
          if loop_end > ticket_max_size
            loop_end = ticket_max_size
            loop_sise = loop_end - loop_start + 1

          for i in [loop_start..loop_end]
            
            #課題を取得
            back_log_url = "https://#{space_id}.backlog.jp/api/v2/issues/#{bl_project_id}-#{i}?apiKey=#{api_key}"

            request = robot.http(back_log_url)
                          .get()
            request (err, res, body) ->

              #上手くパースされないので特殊文字を消す
              body_new = body.replace(/\\n/g, "")
                          .replace(/\\'/g, "'")
                          .replace(/\\"/g, '')
                          .replace(/\\&/g, "")
                          .replace(/\\r/g, "")
                          .replace(/\\t/g, "")
                          .replace(/\\b/g, "")
                          .replace(/\\f/g, "")
              json = JSON.parse body_new
              
              
              #エラーチェック
              if ("errors" of json)
                finish_cnt = 9999
                return 0
              else
                #値が入ってこない時があるので、デフォルト設定
                eh = if json["estimatedHours"]? then json["estimatedHours"] else 0
                ah = if json["actualHours"]? then json["actualHours"] else 0
                assignee = json["assignee"]

                name = if ( assignee? && "name" of assignee) then json["assignee"]["name"] else ".未設定 "

                actual = 
                  name: name
                  estimatedHours: eh
                  actualHours: ah
                  status_unopend: 0
                  status_opened: 0
                  status_treatmentted: 0
                  status_closed: 0

                #チケットの状態を保持する
                if json["status"]["id"] == 1
                  actual["status_unopend"] = 1
                else if json["status"]["id"] == 2
                  actual["status_opened"] = 1
                else if json["status"]["id"] == 3
                  actual["status_treatmentted"] = 1
                else if json["status"]["id"] == 4
                  actual["status_closed"] = 1

                #後でサマリーをしないと値が消えてしまうので
                actual_list.push(actual);

              #処理件数をincrement
              finish_cnt += 1

              #処置が全部終わったら出力する
              #非同期処理の中で1つかこの処理が走らない
              if finish_cnt >= loop_sise

                #ユーザー単位でサマリーする
                output = {}
                for actual, idx in actual_list
                  key_name = actual["name"]
                  if key_name of output
                    data = output[key_name]
                    data["estimatedHours"] += actual["estimatedHours"]
                    data["actualHours"] += actual["actualHours"]
                    data["status_unopend"] += actual["status_unopend"]
                    data["status_opened"] += actual["status_opened"]
                    data["status_treatmentted"] += actual["status_treatmentted"]
                    data["status_closed"] += actual["status_closed"]
                    output[key_name] = data

                  else
                    output[key_name] = 
                      estimatedHours: actual["estimatedHours"]
                      actualHours: actual["actualHours"]
                      status_unopend: actual["status_unopend"]
                      status_opened: actual["status_opened"]
                      status_treatmentted: actual["status_treatmentted"]
                      status_closed: actual["status_closed"]
                
                str = " *-------- #{target_name} プロジェクト実績 --------* \n"

                #出力をする
                for k,v of output
                  str += "   * #{k} *: #{v["actualHours"]}/#{v["estimatedHours"]} H( #{floatFormat(v["actualHours"] / 8, 2) } / #{floatFormat(v["estimatedHours"] / 8, 2)} 日) [ 未:#{v["status_unopend"]}, 中:#{v["status_opened"]}, 済:#{v["status_treatmentted"]}, 完:#{v["status_closed"]} ] \n"
                
                msg.send str

                return 0

    #指定したプロジェクトのサマリーを出力
    call_backlog(target_id, target_start, target_end)

    #ヘッダとしてだす
    msg.send "ちょっと待ってね♪"

出力内容

*-------- XXXX プロジェクト実績 --------*
>   ユーザーA: 0/30 H( 0 / 5 日) [ 未:10, 中:0, 済:1, 完:0 ] 
>   未設定 : 0/1074 H( 0 / 134.25 日) [ 未:109, 中:0, 済:0, 完:0 ] 
>   ユーザーB: 0/0 H( 0 / 0 日) [ 未:0, 中:1, 済:0, 完:0 ]

もうちょっと上手くできるんでは?

Promiseとか使えばコールバックのネストが無くなるんじゃ?

coffeeが1.6だったのでPromiseが使えなかったんです。 どうすれば使えるようになるんですかね・・・。

未設定のユーザーって一番上か下に出たほうが良いんじゃ

作った後に気が付いたんですが、ソートするのがめんどくさくて。。。 最後の出力の所でごにょごにょして対応しようと思ってます。

作ってみて

そいういえば、非同期で送信するんだった。ってので思ったより嵌りました。(Promise使えなかったし)

ちょっと遅いし、Backlogにちょっとした負荷がかかってしまうのが気になるんですが、統計が取れるようになったのは便利ですね~

PodCastで情報集め

音楽だけじゃなかったんですね

バイリンガルニュース

知人に英語の勉強にバイリンガルニュースってPodCastが面白いよって言われて、
ゴシップ的なニュースを英語と日本語でやってるんかなぁって思いつつ、
とりあえず聞いてみたんですが、、、

サイエンティフィックだし、アカデミックだし、下ネタだし、なんか普段触れない興味深い話が多くて嵌ってしまった。

バイリンガルニュース (Bilingual News)

バイリンガルニュース (Bilingual News)

  • Michael & Mami
  • 言語コース
  • ¥0

数年前からしているみたいですねー。 スポンサーがいないらしいので、アプリを月額(300円以下だった)で買って続けてもらえるように支援してます。

正直、英語の部分は分からない事が多いけど、雰囲気で分かるようになるのかな??

mosaic.fm

NginxとかLet’s EncryptとかES7とか面白い話が沢山。 後ろで小さく音楽が流れてるのが気になる。 そんな番組。

mozaic.fm

CodeLunch.fm

こっちも同じような感じ。ちょっとだけサーバーサイドよりの話が多いかもかも。

codelunch.fm

Rebuild.fm

有名なやつ! この前、バイリンガルニュースとクロスしてた。

rebuild.fm

ただですよ

内容が内容だけに、聞くことに必死になって、作業しながらとかできないの。 みんな聞き流すのかな。不思議。

phpのオレオレフレームワークを作ろう - Node.jsを試す(準備)

Node.jsを使ってファイルを今風にしよう

悲しいかな・・・

NodeってことはNodeBrewか?nvmか? MacLinuxか? って思われますよね。ですよね。

でも使うのはWindowsです。 田舎のしがないプログラム屋はwindowsなのです。Officeがちゃんと動くんです。えぇ。

なので、Nodistですね!

とにかく、Node.jsいれましょう

Node.jsってなんの役に立つん?

サーバーサイドばかりやってると

  • jsってAjaxでアクセスしてくるあいつでしょ?
  • DOMの内容を書き換えたりするやつでしょ?
  • ブラウザで動くのにサーバーサイドって?

という感じですの。

感じとしては、

APサーバーを立てなくても動くPHPみたいなもん。

です。

Q: でも、PHPのF/W作るんじゃないんか?何で使うん?  
A: なんか知らんけど、
・CSSのメタ言語(SCSS -> CSS)とか
・jsのトランスパイラ(最新言語仕様でjs書いて、今ブラウザに実装されているjavascriptに書き直してくれるやつ)とか
・画像の圧縮とか
。パッケージ化とか

ができるようになって、画面周りを作るのが楽になるらしい。

NodeやなくてNodist?

Node.jsはnpmってのでライブラリを管理しているんだと。 で、バージョン依存とかがあるので、Nodeのバージョン入れ替えたりする必要がある事が多いそうだ。 なので、Nodistを入れて切り替えれるようにしているんだって。

って事でインストール

github.com

Githubの下の方に書いてるREADMEの所に、with the installer ってあるので、 そのリンク先からファイルを落としてきて、YESマンになってインストール

終わったら

コマンドライン

nodist dist

って実行するとズラーって何かが出てくるけど、それはバージョンだ。

nodist use 7.8.0

って感じでコマンドを叩くと、なんかダウンロードみたいなのがされる。 終わったら、nodistって叩くと7.8.0と表示される。インストールできたぽい。

node -v

ってやるとv7.8.0って出る。

完了だ。簡単や。

同時に、パッケージ管理のnpmも入っているんやが己が古い時があるので、更新コマンドを叩く。

npm update -g npm

うむ、これでNode使う準備はできたの。

エディタを入れて置く

AtomとかSublimeとかあるが、phpdocとかがポップアップで表示されるという機能が嬉しかったので、VS Codeを入れて使っていくよ。 エレクトロンで作られているらしくて、Linuxでも使える。 Extentionで設定とかもクラウドで保存できるし、イイね。 code.visualstudio.com

という事で、、、

次はサーバーサイドの環境を作っていこいう。 そう、Dockerは使わないで、XAMMPだ。。。 サーバーは簡単に作りたいもんね。うんうん。

ウッドパームレストを自作してみる - その2

ということで、仕上げ作業

まずは色をいい感じにするゾ

前回、やすりをかけて整形が終わったので、今回はオイルで色塗り。

濃いめの色が好きなのでオーク色のオイルステインを布で塗りぬり。

4回ぐらい塗りぬり。ダークオークみたいにならないかな。。。

ほんで、1時間ぐらい乾かして。。。

滑り止め装着!

木工用ボンド!ゴムは角を丸くしてなんかそれっぽく。

f:id:Bee_Flim:20170330133819j:plain

装着してみる!!

結構な高さがあるキーボード

f:id:Bee_Flim:20170330133804j:plain

寄せてみるととこんな感じ。

f:id:Bee_Flim:20170330133755j:plain

いい高さですね。ほぼぴったり。

f:id:Bee_Flim:20170330133744j:plain

使用感

とても良好です
角度が有りと無しの2種類を作ってみたんですが、FILCO純正の様に手前に角度が結構ついてるほうが使いやすくて、
角度無しだとちょっと手の位置が高い気がする。5ミリぐらいの差だけど結構でかいんだなぁ。
次作る時は気にしてみよう。

板:数百円ぐらい サンドペーパー:200円ぐらい ゴム:120円ぐらい オイルステイン:500円ぐらい(結構残ります)

千円行かないぐらいでできたよ!

ウッドパームレストを自作してみる - その1

なんか腕がだるい

仕事に使う道具は良い物が良いと思って、初めて高級キーボード(FILCO)購入して、
これはいいなぁとカチャカチャ鳴らしながらタイピングしていたんだけれども、
今まで使っていたキーボードと違いちょっと段差が大きいのである、このキーボード。

電気屋で良く見る腕を置くやつ。今までは高さがあんまりないキーボードばかり使っていたので需要が解らなかったけど、こういう時にいるのか!
って調べてみると、FILCO専用のがあるじゃないですか!しかもウッディでいい感じ。

・・・

ただの木の板のくせに高い。。。

いっその事、作ってみよう

昔に棚を作ったあまりの木の板があるので、そいつを使って作っちゃおう。
そうすれば、サンドペーパー代ぐらいやん。

って事で、調査や!

FILCO用のを写真で見てると半分ぐらいから少し厚みが薄くなってるのね。タイプする時の角度用にちょっとつけてるのかな?
木のサイズは440×81×20㎜(底部クッション含む) なので、偶然丁度いい感じの厚みと長さ。

仮にそのままで使ってみると、重量が無いせいか滑って動いてしまうので、ゴムとかを付けて滑り止めを付けないといけないですね。

設計はできたので後はやるのみ!

結構削りそうなので、40~80ぐらいの数枚と、細かく削るために120、化粧磨きで240のサンド。滑り止めのゴム1枚をホームセンターで購入。

f:id:Bee_Flim:20170329212608j:plain

後は、粗いサンドでひたすら角をとる。

f:id:Bee_Flim:20170329212508j:plain

ひたすら削る。

・・・。

ひたすら傾斜をつける。

・・・。

出来た!!

f:id:Bee_Flim:20170329212558j:plain

あとは、オイルを塗って滑り止めをつければ、完成だ!!

けど、もう夜なのでまた来週にもちこしし