LaravelのMacroを使ってBuilderに機能を追加する方法
どうも、くずき(@kzkohashi)です。
先日、正しいJSON API
のフォーマットにするために以下のライブラリを導入した。
paginateは正しいクエリーパラメーターを取っていない
恥ずかしながら最近知ったのだが、Eloquent
で使用しているpaginate
は、完璧なJSON API
のフォーマットに対応していない。
ページ番号で使われるpage=2
などのクエリパラメーターは、正しくは、page[number]=2
にすべきみたいだ。加えて、ページ番号だけでなく、そのページにどれくらい表示させたいかなどはpage[size]=20
で表現する。
なので、導入前と導入後ではこうなる。
導入前
xxx.com/xxx?page=2&size=20
// sizeに関しては独自実装
導入後
xxx.com/xxx?page[number]=2&page[size]=20
さらにきになる方は以下を参照。
Note: JSON API is agnostic about the pagination strategy used by a server. Effective pagination strategies include (but are not limited to): page-based, offset-based, and cursor-based. The page query parameter can be used as a basis for any of these strategies. For example, a page-based strategy might use query parameters such as page[number] and page[size], an offset-based strategy might use page[offset] and page[limit], while a cursor-based strategy might use page[cursor].
JSON API — Latest Specification (v1.0)
ライブラリの実装を見て見る
ここからが本題。
このライブラリを導入し、設定ファイルを書くとjsonPaginate()
が使えるようになる。
YourModel::where('my_field', 'myValue')->jsonPaginate();
どうやって実装したんだろうとコードをみて見るとBuilder::macro
によって独自の関数を追加できるみたいだ。
<?php ... Builder::macro(config('json-api-paginate.method_name'), function (int $maxResults = null, int $defaultSize = null) { $maxResults = $maxResults ?? config('json-api-paginate.max_results'); $defaultSize = $defaultSize ?? config('json-api-paginate.default_size'); $numberParameter = config('json-api-paginate.number_parameter'); $sizeParameter = config('json-api-paginate.size_parameter'); $size = request()->input('page.'.$sizeParameter, $defaultSize); if ($size > $maxResults) { $size = $maxResults; } return $this->paginate($size, ['*'], 'page.'.$numberParameter) ->setPageName('page['.$numberParameter.']') ->appends(array_except(request()->input(), 'page.'.$numberParameter)); });
これはLaravel
のMacro
という機能で、Laravel
が提供している機能に独自のメソッドなどを拡張実装できるみたい。
Builder
以外には、HTML
, Response
などを拡張できる。
他にどう使えるか?
例えば、Eloquent
が返すModel
ではなく、独自に実装したEntity
を返すcollection
を用意するとか
<?php Builder::macro('toDomainCollection', function () { /** @noinspection PhpUndefinedMethodInspection */ return $this->get()->map(function (Domainable $model) { return $model->toDomain(); }); });
レスポンスである条件の時に決まった内容などを送る時とか
<?php Response::macro('notPermitted', function ($type = null) { if (strtolower($type) === 'success') { return $this->json(['data' => []], 200); } return $this->json(['data' => []], 403); });
今まで作ってたやり方を見直すだけでも色々ありそーだなーと思います。
キカガクで機械学習を学んだことを振り返る(~機械学習における流派の違い~)
どうも、くずき(@kzkohashi)です。
知り合いのエンジニアが、去年からブログを毎月20くらい書いてて、フォロワーも何倍も増え、毎月のPVも1万くらいあるみたいで継続は力なりだなと思う今日頃ごろ。
2017年の4月ごろ、キカガクがやっている、機械学習の初級者向けのブラックスボックスセミナーというものを受けた。
先に感想いっておくと、すごくよかった。機械学習において、理論を知るためには「数学」というハードルが常に付きまとうのだが、そのハードルをだいぶ下げてくれて、色々な本の数式やらが気持ち読めるようになってきた。
あと、基本紙に書かされるので、2日間で30〜40枚くらいノート使いました。笑
去年より値段上がってるけど、セミナーの内容も洗練されているだろう。しかも他のセミナーも増えているので興味ある人は是非。
とおもったら、Udemyで初級/中級(こないだ出たばかり)があるっぽくて、目次だけ見た感じこっち受けて見てもいいかも
本題に戻りますと、今回は受けたセミナーを元に内容を復習していきたいと思う。流れとしてはこんな感じ。
- イントロ
- 機械学習における、流派の違い
- 内挿と外挿について
- 微分について
- 単回帰
- 線形代数
- 重回帰
- 最近の分析手法
- ニューラルネットワーク
- ディープラーニング
授業の内容全部は覚えてないのとオリジナルっぽいところは言わないようにしてるので興味ある人はセミナーかUdemy受けた方がいいです!
なぜ受けたのか?
僕は一昨年ごろから時代の荒波にのまれ、機械学習を勉強しないとなと思い色々な書籍を読んだ。研究室ではクラスタリングや簡単に機械学習を学んでいたのもあって、概念や実装自体はなんとなくわかってたような気がしてしまっていた。
実際は、ビジネスで活用って話になるとあまり理解できず、そもそもこの手法がなんでいいのか、数学的に解いたことがないため本当になんでこれがこうなるのかがわからずにモヤモヤしてた。そんな時に機械学習を数学から丁寧にといてくれるというセミナーをQiita
らへんでみつけ、即応募した。おこづかい制になったので5万円は手痛いたかったけど、時間は金で買うものだ。(と言い聞かせる)
機械学習における、流派の違い
そもそも、機械学習には求め方において流派の違いがある。
線形代数ベースと確率統計ベースとなるものだ。
線形代数ベースは、ディープラーニングやSVMなどの手法がある。比較的線形代数は解きやすいとされていて、初学者はこっちからはいるといいらしい。
確率統計ベースは、MCMCやベイズ統計などがある。線形代数と比べ、解き方が難しく初学者にはきついらしい。これは目からウロコで、自分が買った書籍などを読み返すと確かにやめてしまったあたりは確率統計ベースの話らへんになっている。。
内挿と外挿
機械学習では、規則性を見つける必要がある。ということは過去のデータが必要になる。
コンピューター自身が規則性を式に表すことは、候補が多すぎるため、式の定数を求めるのが非常に難しい。
過去のデータの範囲内の値を求めることを内挿といい、範囲外の値を求めることを外挿という。
内挿内のデータなら予測することは可能であるのに対して、外挿(内挿外)のデータの場合、また定数を求めなくてはいけない。
ここで例をあげると、特定の年代の人たちのアンケートを収集し、他の年代の人たちの結果を予測するものがあるとしよう。 そこで最も重要になるのが、アンケートを取る際に最小値と最大値を意識して取ること。
- 20代(最小)と30代(最大)のアンケートを収集: 40台の予測は外挿になる
- 20代(最小)と60代(最大)のアンケートを収集: 30台の予測は内挿になる
ここらへんは肌感でなんとなくわかる気がする。
ただ、こういったことを知っておかないとデータ取得がもっとも重要な領域なので意識しておきたいところだ。
株価などの時系列データに関しては、値段などが内挿となる。
補足
そもそも、機械学習と統計学の違いってなんだろう?とか他に種類あるのか?って思った時に参考になった記事の紹介。
分析の業界じゃ有名なTJO先生
さらに、機械学習エンジニアやデータサイエンティストの違い、要求されるスキルについて書いた記事も参考になるので是非。 要求スキルのレベル高すぎてやばいけど、目標たてずらかったからこの記事のおかげで目標立てやすくなった。
すでにある程度勉強されている方は、データサイエンティストの知り合いが、このやり方をトレースするとかなり勉強になると思うよといっていたのでこちらも。分析する際に、まずはデータの散らばりや、どういうところに着眼点を置くのかがかなり重要なためそういった話とかもあります。
終わり
区切り悪いのでイントロはこんなもんで。
WEBエンジニア勉強会#5で「Amazon Rekognition」を使った例についてLTしてきた
どうも、くずきです。
昨日は、WEBエンジニア勉強会#5に参加し、LTさせてもらってきた。
この勉強会は、「初心者(LT者も含む)でも大丈夫なコミュニティづくり」をしており、LT初心者の自分にとっては行きやすい勉強会だった。
自分の発表内容と他の方と発表、感想などを書いておこうと思う。
https://web-engineer-meetup.connpass.com/event/75898/
発表資料
テーマは「Amazon Rekognitionを用いてフォロワーの男女比を出す」にした。
これにした理由としては、Amazon Rekogntion
の使ってみた系の例は多いものの、実際に目的があって使ってる例はあまり少ないのでこれにしてみた。
補足
途中のFace APIとAmazon Rekognitionの比較の記事
途中のアーキテクチャの説明にあったプロフを設定してるかいなかの判別方法の記事
反省
- 発表時間が過ぎてしまった
- ちょっと早口めで練習したんだけど、本番だとゆっくりになってしまった
- 他の人の発表見てると、ゆっくりで聴きやすかったのでそもそもゆっくりのほうがいい・・?
- 資料ばかり見過ぎた
- せっかくコントローラーでプレゼン動かすやつ買ったのに、資料ばかり見過ぎた
- ちゃんと前見よう
- あまり参加者の属性を考えてなかった
- 今回はたまたまた難しい内容じゃない(多分)発表になったけど、参加者考えてそれに合わせて作らないとなーと
HTTPレイヤーで行うパソーマンスチューニング
主催者である、OSCAのお話。
基礎的なコンテンツの圧縮やキャッシュ方法についてだった。最近、こういうのめんどくさがって忘れがちだったので非常にタメになる話だった。
とにかく分かりづらいTwelve-Factor Appの解説を試みる
日本で「Twelve-Factor App」について解説している、suke_masaのお話。
クラウドで動くアプリケーションが従うべき12個のベストプラクティスについて提唱された「Twelve-Factor App」についての内容。
そもそもこういうものが提唱されているのは知らなかったのでためになった。発表が聞きやすく、プロやった。。
なんか作ったらプレスリリースを出そう
斎藤さんこと、binbin4649のお話。
ちゃんとサービスを作ったら、プレスリリースを出そうと言う内容。
自分の中ではこれが一番好きな内容だった。プレスリリースをだす裏技的なやり方だったり、売り上げが上がるサービスの作り方も発表後に聞かせていただいて、すごくためになった。
Redashの導入とチームをまたいだ変化の話
同じ名前(漢字は違うけど)の、zuckey_17のお話。
ビジネスチームが開発チームによく頼むデータのアウトプットなどを「Redash」で解決しようと言う内容。
すごくためになる話で、自分もRedash使っているのに、ビジネスチームにうまくアウトプットできてないなーと反省。
Dockerを利用したローカル環境から本番環境までの構築設計
kkoudevのお話。
Dockerのメリットだったり、開発から本番構築のやり方の内容。
自分のチームでは開発環境でしか使っておらず、結構耳が痛い内容・・・笑。本番の設計方法などが書いてあるので、近々新規サービスを作る際に参考にさせていただきます。
非機能要件を考えてみよう!
iwanaga0918のお話。
機能要件以外の、非機能要件についてしっかり向き合って、ちゃんと解決していこうと言う内容。
耳が耳が痛いです。IPAが出している非機能要求グレードなどあるみたいで、さっそく参考にしてみようと思う。ただ、少し情報が古いためクラウドに対応した内容については近々更新があるかもとのこと。
感想
初心者レベルの内容だけじゃなくて、少し難しい内容もあったりして面白かった!
ただ、勉強会の趣旨が初心者メインのためもう少し質問の時間を長めに取ったりして、難しい内容については補足があればなぁと思った。
自分が発表すると、フィードバックもらえたり、懇親会でも覚えてもらったりするので発表は重要なんだなと思った。またあれば行きたい。
CircleCI2.0でDocker Composeをキャッシュする
どうも、くずきです。
以前、CircleCI2.0を試しました。
CircleCI2.0
を使って見たものの、せっかく色々機能があるのに使えてなかったので、今回はWorkflows
を使ってDocker Compose
のキャッシュを利用したいと思います。
目標
Workflows
に適応させるDocker Compose
をキャッシュする- キャッシュした
Docker Compose
をテストで利用する
あとはビルドが早くなればいいな〜と思う。
一応環境
CircleCI設定
.circleci/config.yml
の設定
versin: 2 jobs: generate_cache: machine: true steps: - checkout - restore_cache: keys: - docker-{{ .Branch }}--{{ checksum ".circleci/config.yml" }}--{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }} paths: - ~/caches/images.tar - run: name: Setup docker command: | if [ ! -f ~/caches/images.tar ]; then docker-compose pull db cache docker-compose build app mkdir -p ~/caches docker save $(docker images | awk 'NR>=2 && ! /^<none>/{print $1}') -o ~/caches/images.tar fi - save_cache: key: docker-{{ .Branch }}--{{ checksum ".circleci/config.yml" }}--{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }} paths: - ~/caches/images.tar test: machine: true steps: - checkout - restore_cache: keys: - docker-{{ .Branch }}--{{ checksum ".circleci/config.yml" }}--{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }} paths: - ~/caches/images.tar - run: name: docker load command: | if [[ -e ~/caches/images.tar ]]; then docker load -i ~/caches/images.tar fi - run: name: Setup Laravel command: | docker-compose run app composer install cp .env.circleci .env docker-compose run app php artisan key:generate docker-compose run app php artisan migrate:refresh --seed - run: name: Run Test command: | docker-compose run app vendor/bin/phpunit workflows: version: 2 build_and_test: jobs: - generate_cache - test: requires: - generate_cache
各設定箇所を簡単に説明。
workflows
jobs
に書かれたジョブの実行順序を決めたり、並列化などできる。
本家の画像をパラパラと見れば理解しやすい(英語読まない)。
今回は、generate_cache
-> test
というジョブの順番で行うようにしてある。
save_cache
paths
で設定した内容をkey
の名前で保存できる。
key
に関しては{{}}
を使うことで色々な名前にできて、今回は以下を設定。
- .Branch
- ブランチ名ごとにキャッシュ
- checksum ファイル名
- 指定したファイル名が変更されるたびにキャッシュ
- 今回は、
docker
ファイル周りとcircleci
のファイル
restore_cache
save_cache
で指定したkey
で保存したファイルをダウンロードできる。
[タスク] Setup docker
docker-compose
でpull
とbuild
したイメージを
docker save イメージ名 -o アウトプット先
という形で保存している。
ここは結構詰まったので、後述。
[タスク] docker load
generate_cache
ジョブで作ったファイルを、test
ジョブでrestore_cache
し、
それをdokcer load
で読み込んでいる。
動作結果
以前のビルド 結果: 3分07秒
新しいビルド 結果: 2分52秒
結果あんまり早くならなかった。
原因としては、ジョブを切り替える時間、restore_cache
などの時間、composer install
した内容をキャッシュしていなかったためである。
まあでもかっこよくなったのと、並列化などしやすくなったためよかった。
詰まったところ
キャッシュ名に.circleci/config.yml
を入れてなかった
すごく恥ずかしいんですけど、キャッシュをする際にkey
に.circleci/config.yml
をいれなかったせいで、.circleci/config.yml
をいくら変更しても前のキャッシュを使用してて全然更新されてなかった。。
docker saveが上手くされてなかった
最初はこのようにsave
してた。
docker save -o ~/caches/images.tar
これだと、docker load
はできるものの、実際にdokcer-compose run
を行う際にキャッシュを使わずに一からPull&Build
をしてしまう。だいぶここにはまった。
docker load
しても、Rpository
とTag
の名前が<none>
になってしまい、Pull
する際などに取れないからである。
解決法としては、docker images
する際に、Repository
名を切り出して保存する。
docker save $(docker images | awk 'NR>=2 && ! /^<none>/{print $1}') -o ~/caches/images.tar
ほんと助かりました(ここのコードちょうだいしました・・)。作者様ありがたです。
もう少し良さげなコマンド無いか探して見まする。
ビルドしたDockerFileに名前をつける
docker-compose.yml
app: #image: php:7.1.1-apache build: . image: app container_name: app ports: - "8000:80" volumes: - .:/var/www/html - ./000-default.conf:/etc/apache2/sites-available/000-default.conf depends_on: - db - cache
http://docs.docker.jp/compose/compose-file.html#build
build
した際に、image
でタグ名をつけとかないとdocker save
時に困ります。
まとめ
workflows
でかっこよいテストにできるrestore_cache
,save_cache
を使いこなそう- ハマりどころは細かくあるので注意
- 次は並列化したい(そもそもテストの量増やさないと・・・)
Laravelで日ごとに変わるランダムなソートをする
どうも、くずきです。
こないだ、セールスの方からリストの表示をランダムでだせないのかというご要望がありまして、実装してみた話です。
DB側でランダムで出す
基本的にソートはSQL
側で行なっているため、なるべくDB
側でできないかな〜と思ってたらありました。
SELECT * FROM table ORDER BY RAND();
ORDER BY RAND()
をつけるだけで、ランダムになるというすごく便利な代物。
このままだと毎回変わってしまうため、引数にseed
をつければランダムだけと固定にできる。
SELECT * FROM table ORDER BY RAND('1234556778');
EloquentでORDER BY RAND()を呼ぶ
Eloquent
なら絶対実装してくれてる!!!と思ったら流石ありました。
https://stackoverflow.com/questions/13917558/laravel-eloquent-or-fluent-random-row
Model::inRandomOrder()
バージョンによって書き方は変わるみたいだけど、5.2
以上はこれでいけるみたい。
ただ、このままだと先ほども書いたとおり、毎回読み込むたびに変わってしまう。
1
ページならいいけど、実際には->paginate()
など使ってページネーションしてる場合がほとんだと思う。2
ページ目をみにいったのに、新しくランダムになってしまうせいで1
ページ目に表示されたものが出てしまうなんてこともあり得るだろう。
なので、こちらも引数をつけて固定にする。
Model::inRandomOrder(1234567)
日ごとにソートさせる
ランダムの値を固定のシードで固定しまったら、テーブルのレコードが増減しない限り固定な訳で・・、日ごとにランダムにさせよう!と思いできた最終コード。
$today = Carbon::today()->timestamp; $query->table ->orderBy('status', 'desc') // 別のソートを優先で行なっている ->inRandomOrder($today);
Carbon
で今日のタイムスタンプを取りそれを固定にするやり方で、日ごとに変わるソートの出来上がり。ただ、問題点があり
- 日をまたいだときに新しいランダムになってしまう
- テーブルのレコードが増減するとランダムの順番も変わってしまう
という、どうしもない2点がある。
一つ目の問題点の解決方法は、サーバー側にアクセスした際にシード値をフロント側に返してその値を使いまわすことで解決はできそうだなぁと思う。
二つ目の問題点は、過去のレコードをランダムにし、新しいレコードを1ページ目などに表示する仕組みにすれば良さそう。新鮮なレコード + 過去のレコードを埋もれさせないって意味ではこちらは近いうちにやっときたい。
感想
プログラム側でゴリらなくても、DB
側でいい感じにランダムにできて楽だった。
シード値もフロントと連携すれば解決するし、新鮮さがないリストに使うにはもってこいなんではないだろうか。
Pretty Result PrinterでPHPUnitのテストを見やすくする
どうも、くずきです。
今日は、PHPUnit
を見やすくるPretty Result Printer
を紹介したいと思います。
Pretty Result Printerとは
そもそも今のPHPUnit
は結果が見辛かったりします。
PHPUnit 5.7.26 by Sebastian Bergmann and contributors. .........................................F........... 53 / 53 (100%) Time: 8.72 seconds, Memory: 32.00MB There was 1 failure: 1) Tests\Unit\App\Services\HttpClientTest::it_returns_correct_xxxx Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'/api/entries/1' +'/api/entries/2' /xxx/tests/Unit/Services/HttpClientTest.php:34 FAILURES! Tests: 53, Assertions: 125, Failures: 1.
ここらへんを改善するためにできたのがPretty Result Printer
で
PHPUnit 5.7.26 by Sebastian Bergmann and contributors. ==> ...erFollowerAttributeForDepartmentStoreTest ✓ ✓ ==> ...ncerFollowerAttributeForDiscountStoreTest ✓ ✓ ==> ...fluencerFollowerAttributeForDrugstoreTest ✓ ✓ ==> ...luencerFollowerAttributeForOnlineTest ✓ ✓ ==> ...uencerFollowerAttributeForVarietyShopTest ✓ ✓ ==> ...uencerFollowerScoreForDepartmentStoreTest ✓ ==> ...uencerFollowerScoreForDiscountStoreTest ✓ ==> ...luencerFollowerScoreForDrugstoreTest ✓ ==> ...uencerFollowerScoreForOnlineTest ✓ ==> ...ncerFollowerScoreForVarietyShopTest ✓ ==> ...ns\Services\FollowerChangeRateServiceTest ✓ ✓ ✓ ==> ...Domains\Services\FollowerScoreServiceTest ✓ ✓ ✓ ==> ...it\Domains\ValueObjects\AttributeTypeTest ✓ ✓ ✓ ✓ ==> ...it\Domains\ValueObjects\FollowerCountTest ✓ ✓ ✓ ✓ ✓ ==> Tests\Unit\ExampleTest ✓ ==> App\Http\Requests\GuzzleRequestTest ✓ ==> Tests\Unit\Models\InstagramInsightTest ✓ ✓ ✓ ==> ...s\Unit\App\Services\HttpClientTest ✓ ✖ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ==> ...Unit\App\Services\ResponseJsonTest ✓ ✓ ✓ ✓ Time: 8.18 seconds, Memory: 32.00MB There was 1 failure: 1) Tests\Unit\App\Services\HttpClientTest::it_returns_correct_xxx Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'/api/entries/1' +'/api/entries/2' /xxxx/tests/Unit/Services/HttpClientTest.php:34 FAILURES! Tests: 53, Assertions: 125, Failures: 1.
このように、どこのテストがこけたのがざっくりと見やすくなる。
導入と実行
導入は簡単で、Laravel
のcomposer.json
に追加
"require-dev": { "barryvdh/laravel-ide-helper": "^2.4", "fzaninotto/faker": "~1.4", "mockery/mockery": "0.9.*", "phpunit/phpunit": "~5.7", "codedungeon/phpunit-result-printer": "^0.4.4", // 追加 },
使用方法は簡単で、phpunit.xml
に書いてもいいけどすぐ試すには
phpunit --printer=Codedungeon\\PHPUnitPrettyResultPrinter\\Printer
でさきほどの結果が得られる。
おまけ
Laravel
の開発者であるTaylor Otwell
がツイッターで次のLaravel
のバージョンである5.6
に入れるか検討してるみたい。
Tempted to just add this to Laravel 5.6 phpunit file tbh 🧐 https://t.co/eBuIVP6ci8
— Taylor Otwell 🏄♂️ (@taylorotwell) 2017年12月23日
LaravelでRedshiftにインサートする際にでるSQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "returning"の対処法
どうも、くずきです。
以前、Laravel
でRedshift
を扱う方法について書きました。
今回は、Redshift
にインサートする際にでる
SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "returning"
の対処法について。
EloquentとRedshiftを紐づける
以前の記事ではコードかかなかったけど、Eloquent
にはモデルごとにコネクションを変えることができる。
class User extends Model { protected $connection = 'redshift'; protected $table = 'users'; }
$connection
に該当する接続先を定義してあげれば良い。
Redshiftにインサートする
冒頭に述べたように、Eloquent
ではRedshift
に接続した際に、そのままインサートするとエラーがでる。
User::create(...)
上記のようにcreate
する場合、Eloquent
ではデフォルトで、最後にインサートしたid
を返すクエリーを投げている。
SQL: insert into "user" ("user_id", "updated_at", "created_at") values (335767, 2017-12-28 15:54:04, 2017-12-28 15:54:04) returning "id"
returning id
がその部分だ。
これの解消方法は簡単で、
class User extends Model { // 2つ追加 protected $primaryKey = null; public $incrementing = false; protected $connection = 'redshift'; protected $table = 'users'; }
primaryKey
をnull
にし、incrementing
をfalse
にしてauto increment
の設定をオフにすれば良い。