Hajimari Tech Blog| 株式会社Hajimari 2023-03-27T11:25:48+09:00 itprotech Hatena::Blog hatenablog://blog/10257846132643869105 Gather.townで謎解き迷路型エンジニア交流会を実施しました! hatenablog://entry/4207112889971619031 2023-03-27T11:25:48+09:00 2023-03-27T11:25:48+09:00 Hajimariエンジニア交流会の告知画像 はじめに Gather.townとは? Hajimari内でのGather 謎解き+迷路で交流を深める 工夫した点 イベント風景 おわりに 採用情報 付録 今回使用した謎(一部) 今回のGatherイベント設計時のメモ 謎の答え はじめに Hajimari PROPARTNER事業部所属の上山です! 先日社内のエンジニアメンバーにて、Gather.townの使い方をもっと知ってほしい!という気持ちがありGatherを使用した迷路 + 謎解きイベントを実施いたしました。 今回はそのエンジニア交流会についてご紹介いたします! 最後に付録として謎解きや、今… <p><figure class="figure-image figure-image-fotolife" title="Hajimariエンジニア交流会の告知画像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315114826.jpg" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Hajimariエンジニア交流会の告知画像</figcaption></figure></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#Gathertownとは">Gather.townとは?</a></li> <li><a href="#Hajimari内でのGather">Hajimari内でのGather</a></li> <li><a href="#謎解き迷路で交流を深める">謎解き+迷路で交流を深める</a><ul> <li><a href="#工夫した点">工夫した点</a></li> <li><a href="#イベント風景">イベント風景</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#採用情報">採用情報</a></li> <li><a href="#付録">付録</a><ul> <li><a href="#今回使用した謎一部">今回使用した謎(一部)</a></li> <li><a href="#今回のGatherイベント設計時のメモ">今回のGatherイベント設計時のメモ</a></li> <li><a href="#謎の答え">謎の答え</a></li> </ul> </li> </ul> <h2 id="はじめに">はじめに</h2> <p>Hajimari PROPARTNER事業部所属の上山です!</p> <p>先日社内のエンジニ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%F3%A5%D0%A1%BC">アメンバー</a>にて、Gather.townの使い方をもっと知ってほしい!という気持ちがありGatherを使用した迷路 + 謎解きイベントを実施いたしました。</p> <p>今回はそのエンジニア交流会についてご紹介いたします!</p> <p>最後に付録として謎解きや、今回交流会での企画メモを載せています。Gather.townを使われていてイベント企画に迷った際はお使いください!</p> <h2 id="Gathertownとは">Gather.townとは?</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.gather.town%2F" title="Gather | Building better teams, bit by bit" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.gather.town/">www.gather.town</a></cite></p> <p>2D<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%BF%A5%D0%A1%BC%A5%B9">メタバース</a>オンラインオフィスツールです。</p> <p>Zoomや<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> Meet・Discordと言ったオンライン通話ツールとは一線を画しており、実際に自分の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%D0%A5%BF%A1%BC">アバター</a>を動かしつつ気軽に相手に話かけることができます。</p> <p>Gather.townでは「手をふる」機能だったり、「ベルを鳴らす」機能であったり、リモート接続していてもある程度プライベート空間が保たれるように設計されています。</p> <p>2/21からGather.townの無料枠が同時接続最大10人に縮小されたため、Hajimari社内では課金し利用を続けています。</p> <h2 id="Hajimari内でのGather">Hajimari内でのGather</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.hajimari.inc%2Fentry%2F2022%2F11%2F16%2F120000" title="『Gather.Town』でリアルオフィスを再現して明日からリモート出社する方法 - Hajimari Tech Blog| 株式会社Hajimari" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.hajimari.inc/entry/2022/11/16/120000">tech.hajimari.inc</a></cite></p> <p>以前投稿した記事の6Fオフィスに加えて、2022年12月のHajimariリアルオフィスの増床と共にHajimari社内のGatherオフィスも7F, 9Fが増床されています!</p> <p>最初は現実とGatherのオフィスで位置関係を同じにするといった小さなこだわりを持っていたのですが、しだいに人数規模とマップサイズを考慮しつつ、コミュニケーションをしやすいデザインを考えた結果、現実のオフィスとはリンクしない形に落ち着きました。</p> <p>Hajimari社内の各事業部メンバーが同時に接続している空間であったり、金曜日昼にはオープン雑談会が行われたり、金曜日夜には夜な夜なBarが開催されています。</p> <p>また、各々が自分のデスクが設定されており、作業する際はデスクに籠もりっきりになる人もいます。</p> <p>Gather.townを有効利用することによって、リモートワークにおいても常時接続をすることによって、まるで同じ空間にいるような感覚で仕事をすすめられています!</p> <p><figure class="figure-image figure-image-fotolife" title="オフィスの中央には大きい公園があり、一足早いですが桜も咲いています!!"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315114940.png" width="831" height="635" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>オフィスの中央には大きい公園があり、一足早いですが桜も咲いています!!</figcaption></figure></p> <h2 id="謎解き迷路で交流を深める">謎解き+迷路で交流を深める</h2> <p><figure class="figure-image figure-image-fotolife" title="謎解き迷路イベントの説明資料"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115008.png" width="1200" height="682" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>謎解き迷路イベントの説明資料</figcaption></figure></p> <p>今回はGather.townを通して、謎解きと迷路を組み合わせた催しを行いました!</p> <p>企画段階の時点で、迷路を作る手間が大変だったり、限られた時間の中で解ける謎解きを考えるのが大変でした・・・!</p> <p>謎解き問題については弊社謎解きエンジニアである植本さんにお願いをし、作成していただきました。</p> <p>流れとしては以下のように進めました。</p> <ol> <li>エントランスで全員集まり、絨毯ごとに分かれてチーム分け</li> <li>チームが決まったら、チームごとに迷路へ入る</li> <li>迷路の中では謎解き問題が複数置かれているので、解きつつ迷路を進む</li> <li>一番早く迷路を脱出できたチームが優勝!</li> </ol> <p>想像以上に早く謎を解いてしまうチームがいたり、謎を解かずに問題の雰囲気から直感で解くメンバーがいたり・・・かなり荒れた謎解き交流会となりました!!</p> <h3 id="工夫した点">工夫した点</h3> <ul> <li>謎をすべて解かなくてもゴールはできる</li> <li>最初に難しい謎を置いておくことで解けたら「乗り物」を使えるアドバンテージがある</li> <li>行き止まりがあるが、基本的に最低限解く必要のある謎がある</li> </ul> <h3 id="イベント風景">イベント風景</h3> <p><figure class="figure-image figure-image-fotolife" title="開催中にスクリーンショットを撮るのを忘れてしまったため、自分一人だけですが、このようなエントランスでチーム分けを行いました。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115104.png" width="1193" height="768" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>開催中に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>を撮るのを忘れてしまったため、自分一人だけですが、このようなエントランスでチーム分けを行いました。</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="迷路はファンタジーっぽさもありながら、スラム感も出して「ちょっと変わった雰囲気」を出しました。"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115132.png" width="999" height="817" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>迷路はファンタ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B8%A1%BC">ジー</a>っぽさもありながら、スラム感も出して「ちょっと変わった雰囲気」を出しました。</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="チームで謎を解いている時の写真です。なかなか難しいですね・・・!"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115154.png" width="1200" height="771" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>チームで謎を解いている時の写真です。なかなか難しいですね・・・!</figcaption></figure></p> <h2 id="おわりに">おわりに</h2> <p><figure class="figure-image figure-image-fotolife" title="最後にみんなでわちゃわちゃ記念撮影"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115325.png" width="1200" height="683" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>最後にみんなでわちゃわちゃ記念撮影</figcaption></figure></p> <p>Gather.townは工夫しだいで交流会にも大きな幅が持たせられるということが分かりました。</p> <p>それに加えて、どのように設計するかでGather.townの機能を「遊びながら」理解してくれるかが決まりますし、使う側も「遊びながら」機能を覚えられたほうが健全だと思っています!</p> <p>今回はマップの作成方法といった内容は詳しく記載しませんが、気になった方はぜひ前回の記事を参照してみてください!</p> <p>今後もHajimariではGather.townを使ったリモートワーク下でもオフラインオフィスと変わらない関係を築ける取り組み・交流会の実施を企画しています!</p> <p>リモートワークでもオフラインオフィスと変わらない「繋がり」を持つことで、地域創生へのアプローチを行っていきたいと思っています。</p> <h2 id="採用情報">採用情報</h2> <p>株式会社Hajimariでは、自社開発・受託開発を行なっており、一緒に開発を行なっていただけるエンジニア募集しています!</p> <p>長野拠点の立ち上げメンバーも大募集しています!</p> <p>みなさまとお会いできるのを心からお待ちしております!</p> <iframe frameborder='0' height='305px' name='wantedly_project_widget_802044' scrolling='no' src='https://platform.wantedly.com/projects/802044' style='border: none; max-width: 100%; min-width: 240px; width: 540px;'></iframe> <iframe frameborder='0' height='305px' name='wantedly_project_widget_910364' scrolling='no' src='https://platform.wantedly.com/projects/910364' style='border: none; max-width: 100%; min-width: 240px; width: 540px;'></iframe> <h2 id="付録">付録</h2> <h3 id="今回使用した謎一部">今回使用した謎(一部)</h3> <p>今回、謎解きエンジニアの植本くんから謎解きをこの記事に使って良いという許可を得たため、ぜひこちらでみなさんも解いてみてはいかがでしょうか。</p> <p>※答えは一番最後に記載されています。</p> <p><figure class="figure-image figure-image-fotolife" title="1番目の謎"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115213.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>1番目の謎</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="2番目の謎"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115232.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>2番目の謎</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="3番目の謎"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20230315/20230315115246.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>3番目の謎</figcaption></figure></p> <h3 id="今回のGatherイベント設計時のメモ">今回のGatherイベント設計時のメモ</h3> <pre class="code md" data-lang="md" data-unlink> ## 目的 主目的としては、Gatherの正式導入にあたりGather上での操作に慣れてもらう意図がある。副次的に交流も深める ## 概要 Gatherで迷路を作り、色々操作に慣れながらゴールを目指す。 ## 使用想定機能 - 基本移動・Follow機能 - 物を置く - Xキーでインタラクトする - パスワード付きドアを開ける - 乗り物に乗る - ペットを連れて行く ## 設計 ### Must - 最初にエントランスを用意し、そこでチーム分けを行う - チームに別れたあと、『勇者』を一人決めてもらい、フォロー機能を使ってもらう - 謎解きを道中に置き、Password付きドアに答えを入れるスタイルにする - 全体的に反復させる設計にし、乗り物を用意する - スタート地点に謎解きを置いておく - ポケモンのジムみたいにポータルでポンポン飛ぶ形式にする - みんなでデコるエリア ### Better - プロフィール機能を使って謎を組み込めるとBetter - ゴールでは色みんなで話せるスペースを用意する - 麻雀卓を用意して、イベント後出来るようにしておく ## 開催時の注意点 - 不正防止用の権限削除とグローバルビルドのオフ </pre> <h3 id="謎の答え">謎の答え</h3> <p>上から</p> <ul> <li>時間(hour)</li> <li>思春期</li> <li>あたま</li> </ul> <p>です。解けましたか?</p> y_ueyama 開発合宿を行いました! hatenablog://entry/4207112889959236152 2023-02-13T09:40:49+09:00 2023-02-13T09:40:49+09:00 株式会社HajimariのTUKURUS事業部では、2022年の12月15日と16日の2日間にかけて開発合宿を行いました。 開発合宿をすることになった経緯 私たちTUKURUS事業部は、普段、受託開発やクライアント様の開発チームに業務委託として参画させていただき、開発の支援を行なっております。また、去年の3月に長野県で開発拠点を立ち上げ、徐々に長野から働くメンバーも増えています。 note.com そのため、普段同じプロジェクトで働いていないメンバーやフルリモートで働いているメンバーとのコミュニケーション不足が課題になっていました。そこで、開発合宿を通してメンバー間の交流を深めるとともに、普段… <p>株式会社HajimariのTUKURUS事業部では、2022年の12月15日と16日の2日間にかけて開発合宿を行いました。</p> <h2 id="開発合宿をすることになった経緯">開発合宿をすることになった経緯</h2> <p>私たち<a href="https://www.tukurus.com/">TUKURUS事業部</a>は、普段、受託開発やクライアント様の開発チームに業務委託として参画させていただき、開発の支援を行なっております。また、去年の3月に長野県で開発拠点を立ち上げ、徐々に長野から働くメンバーも増えています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fhajimari_nagano%2Fn%2Fn44656f7b4796" title="はじめまして (株) Hajimari TUKURUS事業部 長野拠点です|Hajimari_TUKURUS_Nagano|note" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://note.com/hajimari_nagano/n/n44656f7b4796">note.com</a></cite></p> <p>そのため、普段同じプロジェクトで働いていないメンバーやフルリモートで働いているメンバーとのコミュニケーション不足が課題になっていました。そこで、開発合宿を通してメンバー間の交流を深めるとともに、普段交流のないメンバーとの開発により新たな知見を得てもらいたいという目的で開発合宿を行うことにしました。</p> <h2 id="合宿の様子">合宿の様子</h2> <p>開発合宿では普段は別のプロジェクトの人同士でチームを組み、与えられたテーマに沿ってアプリを開発しました。今回のテーマは「会社内の業務改善につながるアプリ」でした。slackの分報で流れてしまうナレッジを貯めるアプリや、会社内で気軽に質問ができるようなアプリなど、普段の業務をしていては開発ができないようなアプリが誕生しました。 <figure class="figure-image figure-image-fotolife" title="開発していた時の様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kappy047i/20230201/20230201002110.jpg" alt="&#x958B;&#x767A;&#x3057;&#x3066;&#x3044;&#x305F;&#x6642;&#x306E;&#x69D8;&#x5B50;" width="1200" height="764" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>開発していた時の様子</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="質問アプリの画像"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kappy047i/20230206/20230206192337.png" alt="&#x8CEA;&#x554F;&#x30A2;&#x30D7;&#x30EA;&#x306E;&#x753B;&#x50CF;" width="1200" height="752" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>質問アプリの画像</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="ナレッジ蓄積アプリの概要"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kappy047i/20230131/20230131235342.png" alt="&#x30CA;&#x30EC;&#x30C3;&#x30B8;&#x84C4;&#x7A4D;&#x30A2;&#x30D7;&#x30EA;&#x306E;&#x6982;&#x8981;" width="1200" height="672" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ナレッジ蓄積アプリの概要</figcaption></figure></p> <h2 id="合宿を終えて">合宿を終えて</h2> <p>合宿を終え、参加したメンバーに合宿に関するアンケートを行いました。アンケートの結果から、当初の目的としていたメンバー同士の新たな交流を生むことができたり、新たな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%D7%A5%EA%B3%AB%C8%AF">アプリ開発</a>の機会が得られて良かったという声があがりました。また、次回も参加したいという声も多く、実施して良かったと思います。しかし、テーマに対して開発の時間が足りないという声も多かったため、次回実施する時は、時間割に関して工夫をしていきたいと思います。</p> <h2 id="最後に">最後に</h2> <p>今回はTUKURUS事業部初の開発合宿でしたが、普段オンラインで働いている人と顔を合わせながら開発をしたり、ご飯を食べる良い機会になりました。今後もこのような取り組みを続けていきたいと思っています!</p> <p>株式会社Hajimariでは、自社開発・受託開発を行なっており、一緒に開発を行なっていただけるエンジニア募集しています!</p> <p>長野拠点の立ち上げメンバーも大募集しています!</p> <p>みなさまとお会いできるのを心からお待ちしております!</p> <iframe frameborder='0' height='305px' name='wantedly_project_widget_802044' scrolling='no' src='https://platform.wantedly.com/projects/802044' style='border: none; max-width: 100%; min-width: 240px; width: 540px;'></iframe> <iframe frameborder='0' height='305px' name='wantedly_project_widget_910364' scrolling='no' src='https://platform.wantedly.com/projects/910364' style='border: none; max-width: 100%; min-width: 240px; width: 540px;'></iframe> kappy047i Webブラウザレンダリングの仕組み【DOM・DOMツリーって何?】 hatenablog://entry/4207112889938507856 2023-01-16T12:00:56+09:00 2023-01-16T13:54:34+09:00 こんにちは! 株式会社Hajimariでエンジニアインターンをしている溝口と申します! 今回はブラウザレンダリングの仕組みについて書いていきます! この内容にしようと思ったのは最近、ブラウザ描画周りの知識不足が原因で開発に詰まったことがきっかけです。 これまでずっと理解が曖昧な状態で開発していて、調べることを後回しにしていました。 今回の失敗を機に、曖昧だった部分をしっかり理解しておこうと思い、ブラウザレンダリングについて記事を書くことにしました! レンダリングの流れ 早速ですがブラウザレンダリングについて、説明をしていきます! この記事では、レンダリングとはブラウザがデータを受け取ってから、… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20230116/20230116115424.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!</p> <p>株式会社Hajimariでエンジニア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>をしている溝口と申します!</p> <p>今回はブラウザ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>の仕組みについて書いていきます!</p> <p>この内容にしようと思ったのは最近、ブラウザ描画周りの知識不足が原因で開発に詰まったことがきっかけです。</p> <p>これまでずっと理解が曖昧な状態で開発していて、調べることを後回しにしていました。</p> <p>今回の失敗を機に、曖昧だった部分をしっかり理解しておこうと思い、ブラウザ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>について記事を書くことにしました!</p> <h2 id="レンダリングの流れ"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>の流れ</h2> <p>早速ですがブラウザ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>について、説明をしていきます!</p> <p>この記事では、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>とはブラウザがデータを受け取ってから、表示するまでの一連の動作のことを指します。 ※加えて今回、DOMツリー構築までのHTML Parseなどについては記事が長くなるため省略します。</p> <p>以下が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>の全体像です。</p> <ul> <li>DOM構築</li> <li>CSSOM tree構築</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の実行</li> <li>レンダーツリー構築</li> <li>レイアウト</li> <li>ペインティング</li> </ul> <p>自分は初めて全体像を見た時何一つ理解できてませんでした。</p> <p>1つずつ見ていきましょう。</p> <h2 id="DOMツリー構築">DOMツリー構築</h2> <p>ブラウザを表示するために最初に行われるのがDOMツリー構築です。</p> <p>※HTMLはサーバから送られた状態では表示形式に変換されていないため、DOMツリー構築までにいくつかの工程があるのですが、今回はDOMツリー構築までの流れの説明は省きます。</p> <p>DOMツリーとは <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rehytter/20230116/20230116125002.png" width="561" height="250" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>DOMツリーとはこのようなHTMLを表示するためのデータ構造です。</p> <p>そもそもDOMとは何でしょうか?</p> <blockquote><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Document%20Object%20Model">Document Object Model</a>の略で、HTMLや<a class="keyword" href="http://d.hatena.ne.jp/keyword/XML">XML</a>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a>などで外部操作できるインタフェースを定義したものです。</p></blockquote> <p>つまりDOMというのはあくまで実体の無い<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D5%A5%A7%A5%A4%A5%B9">インターフェイス</a>定義のことで、DOMを元にしてDOMツリーというオブジェクトが作られていくんですね。</p> <p>DOMに基づいて、各ノードがツリー状に作られていきます。</p> <p>親要素がbody、子要素がdiv4つ、孫がp2つずつの場合、このような形になりますね。</p> <p>この時に階層が深ければ深いほど読み込みに時間がかかります。</p> <p>つまり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DE%A1%BC%A5%AF%A5%A2%A5%C3%A5%D7">マークアップ</a>をシンプルに書く方が読み込み速度が上がります。</p> <h2 id="CSSOMツリーの構築">CSSOMツリーの構築</h2> <p>DOMツリーの構築中に<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>ファイルや<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の記述が合った場合、CSSOMツリーというものが構築されます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/rehytter/20230116/20230116125110.png" width="561" height="250" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>DOMツリーと同じツリー状のデータ構造になっています。</p> <p>CSSOMツリーに書かれた内容が、スタイルとして適用されていきます。</p> <p>このCSSOMツリーが作られる際に<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の読み込みを行うのですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の読み込みのルールが少し複雑になっているので、簡単に説明します。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の読み込みのルールを理解するためには、まず詳細度という概念を理解する必要があります。</p> <p>※詳細度を重みと表現しているドキュメントもありますが基本的な意味は同じです。</p> <p>詳細度というのは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>の詳細度と言えます。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>は、同じ要素に対して複数の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>を使っている場合、詳細度の高い<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>が優先されます。</p> <p>詳細度が高いとはどういうことか、具体例を見ていきましょう。</p> <p>こちらをご覧ください。</p> <p>下のh1要素に対して以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>が書かれています。h1要素の文字は何色になるでしょうか?</p> <pre class="code" data-lang="" data-unlink>&lt;div&gt; &lt;h1 class=&#34;title&#34;&gt; ... &lt;/div&gt;</pre> <pre class="code" data-lang="" data-unlink>div h1 { color: green; } .title { color: white; } h1 { color: red; } h1 { color: blue; }</pre> <p>正解は<code>div h1</code>です。文字はgreenのため緑になります。</p> <p><code>h1</code>よりも<code>.title</code>よりも、<code>div h1</code>の方が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>が詳細になっています。そのため、この<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>が優先されます。</p> <ul> <li>クラス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>: クラス名で<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を指定 例:<code>.title</code>、<code>.container</code>など</li> <li>要素<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>: 要素名で<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を指定 例:<code>h1</code>、<code>span</code>など</li> </ul> <p>クラス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>と要素<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>ではクラス<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>の方が優先度は高いのですが、詳細度のある方が優先されるため<code>div h1</code>が優先されます。</p> <p>この詳細度が詳しい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%EC%A5%AF%A5%BF">セレクタ</a>が優先される概念はカスケードと言われます。</p> <p>ちなみに、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>は「<strong>Cascading</strong> Style Sheets」の略です。(自分は<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>のCがカスケードを意味することを初めて知りました。笑)</p> <p>カスケードは英語で「滝のように落とす」という意味で、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>が詳細度の低いものから詳細度の高いものを読み込んでいく特徴を表しています。</p> <p>詳細度の他にも<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>には継承という概念が読み込みの優先度にも関わっているのですが、今回は割愛します。 継承についてはこちら。 <a href="https://developer.mozilla.org/ja/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance#%E7%B6%99%E6%89%BF%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B">https://developer.mozilla.org/ja/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance#%E7%B6%99%E6%89%BF%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B</a></p> <h2 id="JavaScriptの実行"><a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の実行</h2> <p>DOMツリーの構築中にJSファイルやJSの記述が合った場合、その場でJSが実行されます。</p> <p>この時JSの実行が終わるまでDOMツリーの構築はJSの処理が終了するまで中断されます。</p> <p>つまり<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>と同じようにJSの処理が重ければその分画面表示が遅くなるということですね。</p> <h2 id="レンダーツリー構築">レンダーツリー構築</h2> <p>DOMツリーとCSSOMツリーが作成されると、DOMツリーとCSSOMツリーを1つのツリーにまとめます。</p> <p>そうして作られるのがレンダーツリーです。</p> <p>レンダーツリーは画面に表示されるノードのみが作成されます。<code>display: none</code>の付いてるHTML要素やheadタグなどは含まれないという点に注意です。</p> <h2 id="レイアウト">レイアウト</h2> <p>レンダーツリーが作成されたら、viewport内に各ノードのサイズや位置を計算して決めていきます。</p> <p>position: absolute;  width: 33%; などの計算を行います。</p> <h2 id="ペイント">ペイント</h2> <p>レイアウトが済んだレンダーツリーを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D4%A5%AF%A5%BB%A5%EB">ピクセル</a>に変換して、ブラウザ画面に結果を描画します。</p> <h2 id="まとめ">まとめ</h2> <p>以上がブラウザ画面の描画の流れになります。</p> <ul> <li>①DOM構築</li> <li>②CSSOM tree構築(DOM構築に含まれる)</li> <li>③<a class="keyword" href="http://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の実行(DOM構築に含まれる)</li> <li>④レンダーツリー構築</li> <li>⑤レイアウト</li> <li>⑥ペインティング</li> </ul> <p>普段何気なく使っている裏側の処理を知ることの大切さを今回感じました。サイトのパフォーマンス改善をするためにはブラウザの裏側の処理を理解することは必須です。</p> <p>それに今回記事を書く中で、HTMLや<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の書き方が表示速度に直接影響することを知りました。どう動いているのか?どう表示されているのか?と言った仕組みへの理解は良いコードを書くために欠かせない要素だと思います。</p> <p>今回調べたのはブラウザ描画の流れの中の一部分についてなので、更に詳しく調べて全体像をまとめた記事を後日出そうと思います。</p> <p>拙い文章にも関わらず、ご精読ありがとうございました!</p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F1086721" title="24卒エンジニア職|就活が本格化する前にカジュアルにお話ししませんか? by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/1086721">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> rehytter 『Gather.Town』でリアルオフィスを再現して明日からリモート出社する方法 hatenablog://entry/4207112889934303449 2022-11-16T12:00:00+09:00 2022-11-16T12:00:24+09:00 リモートワークでも社内のコミュニケーションを活発化させる為に Gather.Townを使ってリアルオフィスを再現してみました! <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221107/20221107142325.png" width="1200" height="988" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <ul class="table-of-contents"> <li><a href="#GatherTownとは">■Gather.Townとは</a></li> <li><a href="#GatherTownの背景と前景についての前提知識">■Gather.Townの背景と前景についての前提知識</a></li> <li><a href="#必要なツールとソース">■必要なツールとソース</a><ul> <li><a href="#Tiled">Tiled</a></li> <li><a href="#gathertownmapmaking">gathertown/mapmaking</a></li> </ul> </li> <li><a href="#Step0-自社オフィスの見取図を手に入れる">Step.0 自社オフィスの見取図を手に入れる</a></li> <li><a href="#Step1-MapmakingをCloneする">Step.1 MapmakingをCloneする</a></li> <li><a href="#Step2-テンプレートをベースに作っていく">Step2. テンプレートをベースに作っていく</a><ul> <li><a href="#基本機能説明">基本機能説明</a></li> </ul> </li> <li><a href="#Step3-不要なものを消していく">Step3. 不要なものを消していく</a></li> <li><a href="#Step4-作りたいものを作っていく">Step4. 作りたいものを作っていく</a></li> <li><a href="#Extra-Step1-影を付ける">Extra Step1. 影を付ける</a></li> <li><a href="#Extra-Step2-背景を設定する">Extra Step2. 背景を設定する</a></li> <li><a href="#Step5-画像を出力しGatherに設定">Step5. 画像を出力しGatherに設定</a></li> <li><a href="#Step6-Tile-Effectを設定する">Step6. Tile Effectを設定する</a></li> <li><a href="#Step7-細かな効果設定をする">Step7. 細かな効果設定をする</a></li> <li><a href="#おわりに">■おわりに</a></li> <li><a href="#参考資料">■参考資料</a></li> </ul> <p>こんにちは!</p> <p>実は9月から株式会社Hajimariに入社していた<strong>上山</strong>です。</p> <p>インターネットでは <strong>「やむ」</strong> という芸名でやらせていただいております🙏</p> <p>ITプロパートナーズ事業部にて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>マスター兼開発という立ち位置で、チームが幸せに働けるように日々研鑽しています!</p> <p>メインはZennで書いています😉</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fzenn.dev%2Fyum3" title="やむさんの記事一覧" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://zenn.dev/yum3">zenn.dev</a></cite></p> <p>QiitaはQiita Organizationのために使っています😭</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2FYUM_3" title="@YUM_3のマイページ - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/YUM_3">qiita.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Fyum3_tech" title="やむ🐶👉 (@yum3_tech) / Twitter" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://twitter.com/yum3_tech">twitter.com</a></cite></p> <p>Hajimariでは、</p> <p>現在リモートワーク・オフィス出社をその日の予定に合わせて決めることのできる</p> <p>「<strong>365日リモートワークOK制度</strong>」を取り入れており、</p> <p>各メンバーが生産性高く働ける方法を考えて仕事をしております。</p> <p>(詳しくは↓を見ていただければと思います。)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhajimari.inc%2Frecruit" title="hajimari(はじまり)の採用について | 株式会社Hajimari(ハジマリ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://hajimari.inc/recruit">hajimari.inc</a></cite></p> <p>リモートワークを取り入れるにあって大事なのは、</p> <p>やはり<strong>コミュニケーションの取り方</strong>なのではないかと思っております。</p> <p>対面とは違い、気軽にコミュニケーションが取れないようでは</p> <p>リモートワークの利点も十分に活かしきれなくなってしまいます。</p> <p>そこで、今回はリモートワークでも社内のコミュニケーションを活発化させる為に</p> <p><strong>Gather.Town</strong>を使ってリアルオフィスを再現してみました!</p> <p>Gather.Townを使うにあたり</p> <p><strong>「何か特別な技術が必要なんでしょ?」</strong></p> <p>と思われる方もいらっしゃると思います。</p> <p>しかし、今回必要なのは<span style="font-size:180%;">根気</span>です。</p> <p>何時間も少しずつポチポチする根気さえあれば、誰でも実現可能になるように記事を執筆させていただきました。</p> <p>ぜひ読まれた方は、ご自分の根気で自社のリアルオフィスを再現し</p> <p><strong>「社長!Gather.Townでオフィス再現したんで、明日からフルリモートでいいっすか!!!!?」</strong></p> <p>と提案してみてくださいww👍</p> <h2 id="GatherTownとは">■Gather.Townとは</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.gather.town%2F" title="Gather | Building better teams, bit by bit" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.gather.town/">www.gather.town</a></cite></p> <p>もう新進気鋭と言われないくらい、定番のオンラインビデオツールになりました。</p> <p>ご存知でない方にヒトコトで簡単に説明しますと、<strong>「昔の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E9%A5%AF%A5%A8">ドラクエ</a>気分でオンライン通話できる」</strong> ツールになります。</p> <p>「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> Meetでもよくない?」と思われる方もいらっしゃると思いますが、 <strong>「実際に、その場にいる」</strong> オフィス感を出すのであればGather.Townのほうが優位です。</p> <p><strong>朝出社して、自分の席で仕事して、退社時に接続を切る。実際の出社退社のような感覚も味わえる</strong>ため、一体感通してはオススメです。</p> <p><strong>「リモートでずっと繋げっぱなしとかヤバ・・・」</strong> と思われたそこの方、Gatherにはプライベートスペース機能があり、「常に繋ぎっぱなし」という状態ではありません。</p> <p>詳しい説明はここでは省きますが要は <strong>「リモート下でも、一緒に仕事をしている雰囲気」</strong> を作るためのツールです。</p> <h2 id="GatherTownの背景と前景についての前提知識">■Gather.Townの背景と前景についての前提知識</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.gather.town%2Fhelp%2Fbackground-foreground-overview" title="Background &amp; Foreground Overview" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.gather.town/help/background-foreground-overview">support.gather.town</a></cite></p> <p>まず、前景と背景の概念のざっとした説明をします。</p> <p>以下の画像のように</p> <ul> <li><strong>背景</strong>はキャ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ターの下にくるもの</li> <li><strong>前景</strong>はキャ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ターと背景の上にくるもの</li> </ul> <p>になります。これを踏まえて前景・背景を分けて作っていく必要があります。</p> <p><figure class="figure-image figure-image-fotolife" title="前景と背景"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106221921.jpg" width="937" height="1066" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>前景と背景</figcaption></figure></p> <p>Gather.Townの前景・背景共に設定できる画像は以下の条件です。</p> <ul> <li>3200 x 3200 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D4%A5%AF%A5%BB%A5%EB">ピクセル</a>以下</li> <li>最大ファイルサイズ3MB</li> </ul> <p>「どうやって設定するの?」という点ですが、公式の方でドキュメントを準備してくれているため、参照してみてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.gather.town%2Fhelp%2Fbackground-foreground-overview" title="Background &amp; Foreground Overview" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.gather.town/help/background-foreground-overview">support.gather.town</a></cite></p> <p>ざっくり伝えると、下部メニューバーにあるBuild Toolから</p> <p><figure class="figure-image figure-image-fotolife" title="マップ編集へ1"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222027.png" width="348" height="60" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>マップ編集へ1</figcaption></figure></p> <p>Edit in Mapmakerに移動するとドキュメントと同じマップ編集画面に進めると思います。</p> <p><figure class="figure-image figure-image-fotolife" title="マップ編集へ2"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222053.png" width="280" height="697" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>マップ編集へ2</figcaption></figure></p> <h2 id="必要なツールとソース">■必要なツールとソース</h2> <h3 id="Tiled">Tiled</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.mapeditor.org%2F" title="Tiled" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.mapeditor.org/">www.mapeditor.org</a></cite></p> <p>実際に前景・背景画像になるマップを作成していくツールはTiledになります。</p> <p>基本的にはこのツールを使って制作を進めていきます。</p> <h3 id="gathertownmapmaking">gathertown/mapmaking</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgathertown%2Fmapmaking" title="GitHub - gathertown/mapmaking: Maps, tilesets, and assets oh my!" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/gathertown/mapmaking">github.com</a></cite></p> <p>実はGather.Townの公式から既存のマップテンプレートやマップオブジェクトを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Github">Github</a>上で公開されています。</p> <p>基本的にはこのアセットを使って積み木のように積んでいく作業をしていきます。</p> <h2 id="Step0-自社オフィスの見取図を手に入れる"><u>Step.0 自社オフィスの見取図を手に入れる</u></h2> <p>場合によっては一番難しい問題です。会社の見取図やそれに準ずるものを持っていそうな人に聞いて手に入れる必要があります。</p> <p>もしかしたら、ない場合もあります。(その場合は残念ながら根気度が高くなります)</p> <p>この工程をちゃんとこなせるかどうかで制作コストが段違いで変わってきます。</p> <p>ぜひ手に入れられるように全力で努力をしてください🙏</p> <h2 id="Step1-MapmakingをCloneする"><u>Step.1 MapmakingをCloneする</u></h2> <p></u> ここで技術ブログ要素を出しておきます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>git clone git@github.com:gathertown/mapmaking.git </pre> <p>クローンしてきます。<strong>めちゃめちゃ容量が大きいので注意してください!!</strong></p> <p>「Gitの使い方わからないよ><」という方にもZIPでダウンロードする方法を一応載せておきます。</p> <p><figure class="figure-image figure-image-fotolife" title="cloneのしかた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222242.png" width="868" height="720" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>cloneのしかた</figcaption></figure></p> <h2 id="Step2-テンプレートをベースに作っていく"><u>Step2. テンプレートをベースに作っていく</u></h2> <p><code>mapmaking/maps/templates/Office Modern(Skyscraper)/Office Modern12.tmx</code>を今回はベースに作成していきます。</p> <p>新しく作ってもいいのですが、アセットのimport関係がちょっと面倒なのでテンプレートを変えるほうが楽で良いです。</p> <p>テンプレート自体を残しておきたいという方は同じ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ下に複製して名前を変えて編集していきましょう!</p> <h3 id="基本機能説明">基本機能説明</h3> <p>基本的にタイルセットからGather側で用意されているタイルを使用していきます。</p> <p>凄腕デザイナーとかであれば自分でタイルセットなども自作出来るのですが・・・私はそうではないので、既製品をポチポチ積み木していきます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222316.png" width="1200" height="727" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ここらへんのツールを主に使います</p> <p><figure class="figure-image figure-image-fotolife" title="基本ツール"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222426.png" width="1131" height="69" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>基本ツール</figcaption></figure></p> <ul> <li>スタンプ(Bキー) <ul> <li>右クリックで範囲選択、その後範囲選択したものをスタンプとして押せます</li> </ul> </li> <li>消しゴム(Eキー) <ul> <li>右クリックで範囲選択消去</li> </ul> </li> </ul> <p>ここの部分のツールは基礎です。覚えておきましょう👀</p> <p>Command + Sで保存です。これを忘れてしまうと<strong>とてつもなく取り返しのつかない失敗</strong>を引き起こす可能性があるので注意してください。</p> <p>また、レイヤーの概念も重要です。前の章で書いた通り、上にあればあるほど前面に来ます。</p> <p><figure class="figure-image figure-image-fotolife" title="レイヤー基本操作"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222445.png" width="432" height="828" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レイヤー基本操作</figcaption></figure></p> <h2 id="Step3-不要なものを消していく"><u>Step3. 不要なものを消していく</u></h2> <p>レイヤーから装飾となる影などを削除していきます。</p> <p>そうするとテンプレートからは<code>topper</code>と<code>Tile Layer1</code>というものだけになり、<code>topper</code>が前景部分、<code>Tile Layer1</code>が背景部分となります。</p> <p>もちろん多彩な表現をするためにはここからレイヤーを増やしたりするのですが、基本的な根幹部分はこの2つになります。</p> <p>そしたらこの2つのレイヤーの中にある不必要なものもすべて消してしまいましょう!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222508.png" width="1200" height="727" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>見取図など手に入れる事ができた人はぜひ下地として使ってしまいましょう。</p> <p>その際に、まずは<code>base</code>というレイヤーを最下層に作成し、そこに下地を置いておくことをオススメします。</p> <p>【タイルセット】の場所に画像を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E9%A5%C3%A5%B0%26amp%3B%A5%C9%A5%ED%A5%C3%A5%D7">ドラッグ&amp;ドロップ</a>した後に、こちらの設定で取り込んでしまいましょう。</p> <p><figure class="figure-image figure-image-fotolife" title="tileインポート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222557.png" width="653" height="460" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>tileインポート</figcaption></figure></p> <p><strong>注意点として、ちょっと画像が小さいかも・・・と思っても拡大する方法が物理的に画像サイズを大きくするしかありません。</strong></p> <p>他に良いやり方を知っている方がいらっしゃったらぜひ教えていただければなと・・・😭</p> <p>そして下地として画像を読み込んだ姿は下のようになります。</p> <p><figure class="figure-image figure-image-fotolife" title="下地"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222715.png" width="1200" height="577" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>下地</figcaption></figure></p> <h2 id="Step4-作りたいものを作っていく"><u>Step4. 作りたいものを作っていく</u></h2> <p>ツールの使い方を理解しつつ、少しずつ積み上げていってください・・・。</p> <p>注意してほしいのは、コーディングと同じで<strong>いきなり細部にこだわらない</strong>ことです。</p> <p>息切れして挫折する可能性が高くなります。少しずつ、少しずつ積み上げていきましょう。</p> <p>タイルセットは以下を主に使うと思います。</p> <ul> <li><code>Gather Decoration Exterior</code>が装飾品</li> <li><code>floors_3_simple_S20C-5</code>が床タイル</li> <li><code>toppers</code>が天井素材</li> <li><code>walltexture</code>が壁素材</li> <li><code>WindowsObjects</code>が窓素材</li> <li><code>Gather Shadows 1.x</code>が影素材</li> </ul> <p>そして、地道に積み上げられたオフィスはこちらになります。</p> <p><figure class="figure-image figure-image-fotolife" title="半分完成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106225028.png" width="1200" height="808" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>半分完成</figcaption></figure></p> <p>基本的に家具類はGatherに取り込んでから設置するので壁と床設置がメインになります。</p> <p><figure class="figure-image figure-image-fotolife" title="レイヤー説明"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222755.png" width="1140" height="376" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レイヤー説明</figcaption></figure></p> <p>レイヤーの説明ですが</p> <ul> <li><code>object</code> <ul> <li>柱などの<strong>前景に含めたい</strong>オブジェクトを入れています。</li> </ul> </li> <li><code>wall objects</code> <ul> <li>窓などの壁の上に置きたいものを配置しています。</li> </ul> </li> <li><code>over topper</code> <ul> <li>各部屋の区切りとしての黒い細線の部分で使われています。</li> </ul> </li> <li><code>topper</code> <ul> <li>壁が主にここで配置されています。ここまで<strong>前景</strong>で主力する想定です。</li> </ul> </li> <li><code>background topper</code> <ul> <li>壁の下部に当たる部分がここで配置しています。</li> <li>ここから<strong>背景</strong>に含まれる想定です。</li> <li><strong>この分け方をしないと、壁の下にキャラの頭の一部が入ってしまい、微妙に違和感を感じてしまいます。</strong></li> <li>(これ以降の画像ではbackground topperレイヤーは存在していないのですが、テスト段階で気づき手戻りして増やしました)</li> </ul> </li> <li><code>back objects</code> <ul> <li>椅子をここで配置しています。</li> </ul> </li> <li><code>back objects2</code> <ul> <li>椅子が2マス使うケースがあり、隣同士で設置したかったためレイヤーを分けています。</li> </ul> </li> <li><code>carpet</code> <ul> <li>カーペットや壇上などの「床の上に設置したいもの」をここに置いています。</li> </ul> </li> <li><code>floor</code> <ul> <li>床です。</li> </ul> </li> <li><code>base</code> <ul> <li>下書き・ガイド線として使った画像のレイヤーです。後で消します。</li> </ul> </li> </ul> <h2 id="Extra-Step1-影を付ける"><u>Extra Step1. 影を付ける</u></h2> <p><code>shadow</code>レイヤーを一番上(前面)に作成し影を配置していきましょう。</p> <p>タイルセットは<code>Gather Shadows 1.x</code>です。</p> <p>影を付けると急に重厚感が出てきました(そんなに変わったように見えませんが・・・)</p> <p>階段部分は影で上か下に行くかを判別できるようになるので、結構重要な要素かもしれません。</p> <p><figure class="figure-image figure-image-fotolife" title="影なしver"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222818.png" width="1200" height="713" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>影なしver</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="影ありver"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222852.png" width="1200" height="713" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>影ありver</figcaption></figure></p> <h2 id="Extra-Step2-背景を設定する"><u>Extra Step2. 背景を設定する</u></h2> <p>今のままではなんとも言えない悲しさがあります。そうです。背景がないからです。</p> <p>しかし、背景まで一から作っていくとしたら制作が何十倍にも膨らんでしまいます。</p> <p>その上、背景は基本的に見るだけで行けないもの・・・。コストに対するリターンもそれほど大きくありません。</p> <p>なのでここではGatherで使われている背景を使っていきます。</p> <p><code>mapmaking/large splash art/</code>に入っている画像をタイルセットとしてインポートし、使っていきます。</p> <p>超絶雑ですがこんな感じにまとめました。さっきよりは雰囲気がよくなった(と信じたい)です。</p> <p><figure class="figure-image figure-image-fotolife" title="背景あり"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106222936.png" width="1200" height="668" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>背景あり</figcaption></figure></p> <h2 id="Step5-画像を出力しGatherに設定"><u>Step5. 画像を出力しGatherに設定</u></h2> <p>前景出力と背景出力の2ステップを行います。</p> <p>まずは前景から。前景としたいレイヤーだけを表示させほかは非表示にします。</p> <p><figure class="figure-image figure-image-fotolife" title="前景だけ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223029.png" width="1134" height="383" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>前景だけ</figcaption></figure></p> <p>そうしたら、上部メニューバーから</p> <p>ファイル > 画像でエクスポートを選択し、以下のようなウィンドウで「表示レイヤーのみを含める」をチェックします。</p> <p><figure class="figure-image figure-image-fotolife" title="前景画像エクスポート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223055.png" width="1200" height="439" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>前景画像エクスポート</figcaption></figure></p> <p>そしてエクスポートです。これにて前景画像の出力が完了しました。</p> <p>同様に背景画像の出力も行ってください。</p> <p>いよいよ取り込みです。</p> <p>前提知識の章で取り上げた操作方法からマップ編集画面へ移動し、背景画像のインポートと前景画像のインポートを行ってください。</p> <p><figure class="figure-image figure-image-fotolife" title="インポート方法"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223130.png" width="477" height="348" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>インポート方法</figcaption></figure></p> <p>そうすると上手く取り込めます。</p> <h2 id="Step6-Tile-Effectを設定する">Step6. Tile Effectを設定する</h2> <p>今のままだと自由に壁を貫通できたり、行けてほしくない場所に行けてしまいます。</p> <p>なので、ここからは当たり判定を付けてちゃんと行けないように制限を付けていきます。</p> <p>Tile Effectのタブから Impassableを選択し、行けてほしくない場所に設定していってください。</p> <p><figure class="figure-image figure-image-fotolife" title="障害物設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223206.png" width="278" height="650" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>障害物設定</figcaption></figure></p> <p>そしてObjectsから家具を配置していってください。</p> <p><figure class="figure-image figure-image-fotolife" title="家具を配置する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223244.png" width="275" height="651" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>家具を配置する</figcaption></figure></p> <p>ある程度置き終えるとそれなりに形になってきます。いい雰囲気ですね!!</p> <p><figure class="figure-image figure-image-fotolife" title="だいたい完成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223316.png" width="1105" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>だいたい完成</figcaption></figure></p> <h2 id="Step7-細かな効果設定をする"><u>Step7. 細かな効果設定をする</u></h2> <p>会議室で空間を区切れたり、席の近くでしか声が聞こえないようにしたりなど、様々な設定ができます。</p> <p><figure class="figure-image figure-image-fotolife" title="エフェクト説明"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223343.png" width="275" height="651" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>エフェクト説明</figcaption></figure></p> <p>そしてできた完成品がこちらです!</p> <p><figure class="figure-image figure-image-fotolife" title="完成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223430.png" width="1149" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>完成</figcaption></figure></p> <p>概ねいい出来ではないでしょうか🤔</p> <p>前景画像を意識しつつ制作したことにより、柱の後ろにも隠れられるようになりました。</p> <p><figure class="figure-image figure-image-fotolife" title="柱の後ろにも入れる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223454.png" width="145" height="149" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>柱の後ろにも入れる</figcaption></figure></p> <p>前景画像設定のおかげで、天井の梁も表現されているように見えます。</p> <p><figure class="figure-image figure-image-fotolife" title="天井の梁もある"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/y_ueyama/20221106/20221106223517.png" width="145" height="150" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>天井の梁もある</figcaption></figure></p> <p><strong>前景</strong>と<strong>背景</strong>をきちんと意識しながら作ることがGather.townのマップを作る上で重要なことが分かりますね。</p> <p>これを意識しないと手戻りが結構発生してしまうので、精神衛生上あまり良くないです😭</p> <h2 id="おわりに">■おわりに</h2> <p>Gather.townのマップをカスタムすることで、よりチームの結束力を高めることができます。</p> <p>コロナ禍でリモート業務が普及した今、昔の「リアルオフィス」を再現することで今一度盛り上がり、関係性の質を高めてもいいのかもしれません。</p> <p>ハイブリッド勤務を採用している企業では、リアルオフィスの席と座っている人がリンクしているだけで、もし自分がリアル出社した際に「誰が」「どこに」座っているかが分かります。</p> <p>新卒の方で、なかなかオフィスに来れない方に一体感を感じてもらうためにも良いのでしょうか?</p> <p>Gather.townでリモートで希薄になってしまった社員間のやりとりが少しでも活発になれば嬉しい気持ちです。</p> <h2 id="参考資料">■参考資料</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2dgames.jp%2Fhow-to-use-tiled-map-editor%2F" title="Tiled Map Editorの使い方" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://2dgames.jp/how-to-use-tiled-map-editor/">2dgames.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdev.classmethod.jp%2Farticles%2Fcreate-gather-town-foreground-image-with-tiled%2F" title="Gather.Town のカスタムマップで設定出来る前景画像(foreground image)を作成&設定する手順 | DevelopersIO" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://dev.classmethod.jp/articles/create-gather-town-foreground-image-with-tiled/">dev.classmethod.jp</a></cite></p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F1086721" title="24卒エンジニア職|就活が本格化する前にカジュアルにお話ししませんか? by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/1086721">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> y_ueyama 【新人エンジニア】MVCモデルの進化版!? ADRが使いやすかったお話 hatenablog://entry/4207112889909344870 2022-09-28T10:00:00+09:00 2022-09-28T10:00:03+09:00 こんにちは! 7月からインターン生として株式会社Hajimariに入社した、難波 慧人です。 現在は、TUKURÜS事業部で受託開発の業務を行っています! 今行っている案件では、サブスクリプション型動画配信サイトの、新規機能開発・運用保守を担当しています! 開発言語に関しては、 バックエンドはPHP(laravelフレームワーク)を用いており、アーキテクチャはADR(Action Domain Responder)を採用しています。 案件にジョインした当初、MVCアーキテクチャしか知らない私でしたが、ADRの有用性が少しずつ理解できてきました! そこで今回は、MVCアーキテクチャと、ADRアー… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/keitonanba/20220913/20220913204543.png" width="866" height="579" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!<br/> 7月から<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生として<a href="https://www.hajimari.inc/">株式会社Hajimari</a>に入社した、難波 慧人です。</p> <p>現在は、<a href="https://www.tukurus.com/">TUKURÜS</a>事業部で受託開発の業務を行っています! 今行っている案件では、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A5%D6%A5%B9%A5%AF%A5%EA%A5%D7%A5%B7%A5%E7%A5%F3">サブスクリプション</a>型動画配信サイトの、新規機能開発・運用保守を担当しています!</p> <p>開発言語に関しては、 バックエンドは<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>(laravel<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>)を用いており、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>(Action Domain Responder)を採用しています。</p> <p>案件にジョインした当初、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>しか知らない私でしたが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>の有用性が少しずつ理解できてきました!</p> <p>そこで今回は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>と、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>の違いについてご紹介したいと思います!!</p> <p>また、各項目にサンプルコード(ユーザーの一覧、詳細機能)を示していきます!!</p> <h2 id="MVCModel-View-Controllerとは">■<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>(Model View Controller)とは??</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/keitonanba/20220824/20220824091452.png" width="1200" height="679" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> 引用元( <a href="https://white-t007.com/tech/it%E5%9F%BA%E7%A4%8E/mvc/">https://white-t007.com/tech/it%E5%9F%BA%E7%A4%8E/mvc/</a> ) <br></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B6%A1%BC%A5%A4%A5%F3%A5%BF%A1%BC%A5%D5%A5%A7%A5%A4%A5%B9">ユーザーインターフェイス</a>を持つアプリケーションを、モデル(Model)、ビュー(View)、コントローラー(Controller)の3つの責務に分割して、実装していく手法です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Friku-shiru%2Fitems%2F2bed096e106e72e0b58a" title="MVCモデルについて - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/riku-shiru/items/2bed096e106e72e0b58a">qiita.com</a></cite></p> <h3 id="モデルModel">モデル(Model)</h3> <p>モデルとは、アプリケーションの中で、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>、データベースとのアクセスを担っています。例えば、計算、データ取得、データ管理などが挙げられます。</p> <p>Laravel<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>では、Eloquent ORMと呼ばれる、モデルとデータベースの紐付けを行う機能があります。<br /> Eloquent ORMにより、アプリケーションとデータベース間のデータのやりとりをより円滑にすることができます!! <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Freadouble.com%2Flaravel%2F5.dev%2Fja%2Feloquent.html" title="Eloquent ORM 5.dev Laravel" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://readouble.com/laravel/5.dev/ja/eloquent.html">readouble.com</a></cite></p> <ul> <li>サンプルコード</li> </ul> <pre class="code lang-php" data-lang="php" data-unlink>Models/User.php class User extends Model { /** * usersテーブルから全てのユーザーを取得 * * <span class="synPreProc">@return </span>Collection */ public function fetchAllUsers(): Collection { return User::all(); } /** * usersテーブルから指定ユーザーを取得 * * <span class="synPreProc">@return </span>?User */ public function fetchUser($userId): ?User { return User::where('id', $userId)-<span class="synError">&gt;</span>first(); } } </pre> <h3 id="ビューView">ビュー(View)</h3> <p>ビューとは、webサイト上のレイアウトを作成する役割を持っています。主にHTMLで記述されます。また、ビューから生成されたレイアウトはアプリケーションとユーザーの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D5%A5%A7%A5%A4%A5%B9">インターフェイス</a>になっています。</p> <p>ユーザーはこのweb上のレイアウトを通して、アプリケーションにリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを送信、アプリケーションからのレスポンスを閲覧することができます。</p> <h3 id="コントローラーController">コントローラー(Controller)</h3> <p>コントローラーとは、ユーザーからのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トに応じて、モデルとビューの制御を担う部分です。 実際の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B6%A1%BC%A5%A4%A5%F3%A5%BF%A1%BC%A5%D5%A5%A7%A5%A4%A5%B9">ユーザーインターフェイス</a>はこのコントローラーになります。アプリケーションとユーザー間のやり取りは全てここを経由します。</p> <p>ユーザーがwebサイト上で商品一覧をみたい時を例にします。 ユーザーから「商品一覧がみたい」というリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トが来たら、モデルに商品データを取得するように依頼します。次に取得データをビューに渡し、ビューが作成したレイアウトを、webサイト上に表示しています。これによって、ユーザーがweb上で、商品一覧を閲覧することができます。</p> <ul> <li>サンプルコード</li> </ul> <pre class="code lang-php" data-lang="php" data-unlink>Controllers/UserController.php class UserController extends Controller { /** * ユーザー一覧 * * <span class="synPreProc">@return </span>\Illuminate\View\View */ public function index(): View { // Userクラス(モデル)のインスタンス作成 $userModel = new User(); // Userクラス(モデル)のメソッドを使って、ユーザーを取得 $users = $userModel-<span class="synError">&gt;</span>fetchAllUsers(); return view('users.index', ['users' =<span class="synError">&gt;</span> $users]); } /** * ユーザー詳細 * * <span class="synPreProc">@param </span>int $userId * * <span class="synPreProc">@return </span>\Illuminate\View\View */ public function show(int $userId): View { // Userクラス(モデル)のインスタンス作成 $userModel = new User(); // Userクラス(モデル)のメソッドを使って、ユーザーを取得 $user = $userModel-<span class="synError">&gt;</span>fetchUser($userId); // 対象ユーザーが存在しなかったら、404ページを返す if(is_null($user)) { abort(404); } else { return view('users.show', ['user' =<span class="synError">&gt;</span> $user]); } } } </pre> <h3 id="MVCの問題点"><a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>の問題点</h3> <ul> <li><p>コントローラーの肥大化<br /> コントローラーは基本的に、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B6%A1%BC%A5%A4%A5%F3%A5%BF%A1%BC%A5%D5%A5%A7%A5%A4%A5%B9">ユーザーインターフェイス</a>としてアプリケーションの入出力のみを担う責務ですが、コントローラーに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>やデーターベースのやり取りを記述される場合があります。</p></li> <li><p>モデルの肥大化<br /> モデルとは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>・データベースアクセスを書く場所である。しかし、アプリケーションの規模が大きくなると、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>・データベースアクセスも必然的に増えていき、結果モデルは肥大化していきます。。。</p></li> </ul> <h3 id="ADRの導入"><a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>の導入</h3> <p>以上の問題点を踏まえ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>の改良版でもある<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>(Action Domain Responder)を導入しています!!</p> <h2 id="ADRAction-Domain-Responderとは">■<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>(Action Domain Responder)とは??</h2> <blockquote><p>上記の<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>モデルの改良版として、Paul M. Jonesによって提案されたソフトウェア<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3%A5%D1%A5%BF%A1%BC%A5%F3">アーキテクチャパターン</a>です。</p></blockquote> <p>引用元:( <a href="https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder">https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder</a> )</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>では、アクション(Action)、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>(Domain)、レスポンダー(Responder)の3つの責務に分割して、実装していく手法です!</p> <p><a href="https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder">ADR:wikipedia</a></p> <h3 id="アクションAction">アクション(Action)</h3> <p>ユーザーからのHTTPリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを受け取り、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>の処理結果をレスポンダーに渡す役割です。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>でいうコントローラー的な位置付けです。 全てのHTTPリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トはここで処理されます。<br /> <a href="https://wa3.i-3-i.info/word1841.html">HTTPリクエストとは</a></p> <ul> <li>サンプルコード</li> </ul> <pre class="code lang-php" data-lang="php" data-unlink>Actions/User/UserIndexAction.php class UserIndexAction extends Controller { /** * Instance of the Responder property. * * <span class="synPreProc">@var </span>UserRepository * <span class="synPreProc">@var </span>UserIndexResponder */ protected $userRepository; protected $userIndexResponder; /** * Instantiate the class. * * <span class="synPreProc">@param </span>UserRepository $userRepository * <span class="synPreProc">@param </span>UserIndexResponder $userIndexResponder */ public function <span class="synStatement">__construct</span>( UserRepository $userRepository, UserIndexResponder $userIndexResponder ) { $this-<span class="synError">&gt;</span>userRepository = $userRepository; $this-<span class="synError">&gt;</span>responder = $userIndexResponder; } /** * Invoke our action, handle domain, respond. * * * <span class="synPreProc">@return </span>View */ public function <span class="synStatement">__invoke</span>(): View { $users = $this-<span class="synError">&gt;</span>userRepository-<span class="synError">&gt;</span>fetchAllUsers(); return $this-<span class="synError">&gt;</span>responder-<span class="synError">&gt;</span>response($users); } } </pre> <pre class="code lang-php" data-lang="php" data-unlink>Actions/User/UserShowAction.php class UserShowAction extends Controller { /** * Instance of the Responder property. * * <span class="synPreProc">@var </span>UserRepository * <span class="synPreProc">@var </span>UserShowResponder */ protected $userRepository; protected $userShowResponder; /** * Instantiate the class. * * <span class="synPreProc">@param </span>UserRepository $userRepository * <span class="synPreProc">@param </span>UserShowResponder $userShowResponder */ public function <span class="synStatement">__construct</span>( UserRepository $userRepository, UserShowResponder $userShowResponder ) { $this-<span class="synError">&gt;</span>userRepository = $userRepository; $this-<span class="synError">&gt;</span>responder = $userShowResponder; } /** * Invoke our action, handle domain, respond. * * <span class="synPreProc">@param </span>Request $request * * <span class="synPreProc">@return </span>View */ public function <span class="synStatement">__invoke</span>(Request $request): View { $user = $this-<span class="synError">&gt;</span>userRepository-<span class="synError">&gt;</span>fetchUser($request-<span class="synError">&gt;</span>user_id); return $this-<span class="synError">&gt;</span>responder-<span class="synError">&gt;</span>response($user); } } </pre> <h3 id="ドメインDomain"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>(Domain)</h3> <p>アクションから受け取った依頼をもとに、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>、データベースアクセスを担当する役割です。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>でいうモデル的な位置付けです。<br /> <a href="https://qiita.com/os1ma/items/25725edfe3c2af93d735#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%93%E3%82%B8%E3%83%8D%E3%82%B9%E3%83%AB%E3%83%BC%E3%83%AB">ビジネスロジックとは</a></p> <p>今回は、Domainをさらにサービス(Service)、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>(Repository)、モデル(Model)に分けています。</p> <ul> <li><p>サービス(Service)<br /> 主にアプリケーションビジネスルールを記述する。<br /> (アプリケーションビジネスルール = システムであるがゆえに発生するビジネスルール)<br /> 例)データに対する複合処理、データ加工など</p></li> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>(Repository)<br /> データベースとの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%D5%A5%A7%A5%A4%A5%B9">インターフェイス</a>として使用。<br /> 例)データベースアクセスのみ(加工はサービスの責務)</p></li> <li><p>モデル(Model)<br /> 主に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%BF%A1%BC%A5%D7%A5%E9%A5%A4%A5%BA">エンタープライズ</a>ビジネスルールを記述する。<br /> (<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%F3%A5%BF%A1%BC%A5%D7%A5%E9%A5%A4%A5%BA">エンタープライズ</a>ビジネスルール = アプリケーション都合でないビジネスルール)<br /> 例)野球  勝敗:点数が多い方が勝ち 時間:9回で終了 試合人数:各チーム9人ずつ</p></li> <li><p>サンプルコード (今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>がない為、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>のみ)</p></li> </ul> <pre class="code lang-php" data-lang="php" data-unlink>Domain/User/UserRepository.php class UserRepository { /** * usersテーブルから全てのユーザーを取得 * * <span class="synPreProc">@return </span>Collection */ public function fetchAllUsers(): Collection { return User::all(); } /** * usersテーブルから指定ユーザーを取得 * * <span class="synPreProc">@param </span>int $userId * * <span class="synPreProc">@return </span>?User */ public function fetchUser($userId): ?User { return User::where('id', $userId)-<span class="synError">&gt;</span>first(); } } </pre> <h3 id="レスポンダーResponder">レスポンダー(Responder)</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>の処理結果を受け取り、HTTPレスポンスを作成、処理を担当します。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a> モデルのビューとは異なり、httpステータスの変更、クッキー操作も行います。</p> <ul> <li>サンプルコード</li> </ul> <pre class="code lang-php" data-lang="php" data-unlink>Responders/User/UserIndexResponder.php class UserIndexResponder { protected $view; public function <span class="synStatement">__construct</span>(ViewFactory $view) { $this-<span class="synError">&gt;</span>view = $view; } /** * 単体動画の表示 * * <span class="synPreProc">@param </span>Collection $users * * <span class="synPreProc">@return </span>View */ public function response(Collection $users): View { return $this-<span class="synError">&gt;</span>view-<span class="synError">&gt;</span>make( 'users.index', <span class="synStatement">compact</span>('users') ); } } </pre> <pre class="code lang-php" data-lang="php" data-unlink>Responders/User/UserShowResponder.php class UserShowResponder { protected $view; public function <span class="synStatement">__construct</span>(ViewFactory $view) { $this-<span class="synError">&gt;</span>view = $view; } /** * 単体動画の表示 * * <span class="synPreProc">@param </span>?User $user * * <span class="synPreProc">@return </span>View */ public function response(User $user): View { if (is_null($user)) { return \App::abort(404); } else { return $this-<span class="synError">&gt;</span>view-<span class="synError">&gt;</span>make( 'users.index', <span class="synStatement">compact</span>('users') ); } } } </pre> <h3 id="MVCからの改善点"><a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>からの改善点</h3> <ul> <li><p>コントローラーの肥大化<br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>のコントローラーでは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B6%A1%BC%A5%A4%A5%F3%A5%BF%A1%BC%A5%D5%A5%A7%A5%A4%A5%B9">ユーザーインターフェイス</a>として、全てのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト、レスポンスを処理していました。<br /> しかし、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>ではリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トをアクション、レスポンスをレスポンダーといったように、責務を分けることで改善されています。<br /> また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>では、1アクションに対し1クラスしか作成しません。これにより、1つのコントローラーにメソッドが集中することを避けれます!!</p></li> <li><p>モデルの肥大化<br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>のモデルでは、全ての<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>の責務を担当していましたが、サービス(Service)、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>(Repository)、モデル(Model)という責務に細分化することで、モデルの肥大化を防ぎます。</p></li> <li><p>保守性、可読性の向上<br /> 実際、コードリーディングを行う際に、処理の流れが明確なので追いやすいです。<br /> また、データーベースアクセスが一目でわかる、追いたい処理を探す時にもファイル特定がしやすい(サービス層の処理ならDomain配下のserviceファイルを探せばいい)などの利点がありました!</p></li> </ul> <h3 id="まとめ">■まとめ</h3> <p>以上が、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>の違いでした!!</p> <p>最初は、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>ってなんぞや??」と戸惑いましたが、</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>をある程度理解していれば、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>のキャッチアップもやりやすいと思います!</p> <p>是非とも<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>(Action Domain Responder)を導入してみてはいかがでしょうか?</p> <p>Fatなコントローラー 、モデル内で、無限スクロールする開発生活から解放されるはずです。。。</p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F1086721" title="24卒エンジニア職|就活が本格化する前にカジュアルにお話ししませんか? by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/1086721">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> keitonanba エンジニア交流会やってみた!! hatenablog://entry/4207112889912863579 2022-09-21T10:00:00+09:00 2022-09-21T10:00:02+09:00 こんにちは! 2022年5月から株式会社Hajimariでエンジニアをしています、植本です。 現在は、TUKURÜS事業部で主に受託開発の業務を行っています。 今回は先日行った、弊社の23卒内定者を含めた全エンジニアでの「エンジニア交流会」について書いていきます! というのも、、、 他事業部の社員と話すことがあんまりない (内定者から)オンラインだと距離感が掴めなくて気軽に話せる人は実は少ない・・・ という声を聞いていました…! リモ―トワークが普及しつつある今、会社によって社員同士の交流の頻度が減っているところもあると思います。 確かにこれじゃ何かあっても殆ど話したことない人に気軽には相談し… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kekeke21/20220913/20220913234500.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> こんにちは!</p> <p>2022年5月から株式会社Hajimariでエンジニアをしています、<a href="https://twitter.com/I_am__Keisuke">植本</a>です。 現在は、TUKURÜS事業部で主に受託開発の業務を行っています。</p> <p>今回は先日行った、弊社の23卒内定者を含めた全エンジニアでの<strong>「エンジニア交流会」</strong>について書いていきます!</p> <p>というのも、、、</p> <ul> <li><p>他事業部の社員と話すことがあんまりない</p></li> <li><p>(内定者から)オンラインだと距離感が掴めなくて気軽に話せる人は実は少ない・・・</p></li> </ul> <p>という声を聞いていました…!</p> <p>リモ―トワークが普及しつつある今、会社によって社員同士の交流の頻度が減っているところもあると思います。</p> <p>確かにこれじゃ何かあっても殆ど話したことない人に気軽には相談しにくいですよね…。(そりゃそう)</p> <p>ということで(?)</p> <p>エンジニア交流会を開催したので、その様子を一挙大公開します!!!</p> <blockquote><p>※社員同士の交流は最低限でいい、という方もいると思います。 その考え方も尊重しているので、今回の交流会は「強制」ではなく「自由参加」です!</p> <p>※でも、都合が合わない方以外はほぼ全員参加してくれました、本当にありがとうございます(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%BF%B4%EE">歓喜</a>)</p></blockquote> <p>さてさて前置きが長くなりましたが、、、</p> <blockquote><ul> <li><p>株式会社Hajimariのエンジニア組織がどんな雰囲気か知りたい!</p></li> <li><p>弊社エンジニアのリアルな様子が知りたい!</p></li> <li><p>弊社の個や組織へのこだわり/想いが知りたい!</p></li> </ul> </blockquote> <p>こんなことが気になる方は是非読んでみてください!!</p> <p>※今回の記事は技術的なことは書いてありませんのでご了承ください、、、</p> <ul class="table-of-contents"> <li><a href="#Hajimariの社員ってどんな人がいる">Hajimariの社員ってどんな人がいる?</a></li> <li><a href="#なぜエンジニア交流会を行ったのか">なぜエンジニア交流会を行ったのか</a></li> <li><a href="#エンジニア交流会の内容">エンジニア交流会の内容</a><ul> <li><a href="#1-一致するまで終われまテン">1. 一致するまで終われまテン</a></li> <li><a href="#2-wevox-value-card">2. wevox value card</a></li> <li><a href="#3-Will語り会">3. Will語り会</a></li> </ul> </li> <li><a href="#終わりに-弊社が大切にしている自己理解と他者理解組織理解">終わりに ~弊社が大切にしている自己理解と他者理解、組織理解~</a></li> </ul> <h3 id="Hajimariの社員ってどんな人がいる">Hajimariの社員ってどんな人がいる?</h3> <p>そもそも弊社やその社員についてどのような印象でしょうか。</p> <p>自分と仲間の決断と行動、その結果を受け入れて前に進んでいくことのできる人ばかりです!本当に人の良さは声を大にできるのですが、、、</p> <p>ここで語っても中々イメージ湧かないと思うので最近の記事を紹介しておきますね!(是非覗いてみてください!)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F362008" title="【前半】Youは何しにHajimariへ?-内定者が新卒の先輩方へリアルを迫る- | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/362008">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F366009" title="【後半】Youは何しにHajimariへ?-新卒の先輩方へリアルを迫る- | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/366009">www.wantedly.com</a></cite></p> <h3 id="なぜエンジニア交流会を行ったのか">なぜエンジニア交流会を行ったのか</h3> <p>嗚呼、また前置きが長くなってしまったorz</p> <p>端的に言うと、、、</p> <p><strong>新しいメンバーも増えてきたので、自己理解と相互理解を深めて自分の目指すべき方向性を再確認してもらうため</strong></p> <p>にエンジニア交流会を開催することにしました!</p> <p>弊社は2015年設立の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D9%A5%F3%A5%C1%A5%E3%A1%BC%B4%EB%B6%C8">ベンチャー企業</a>で、社員数は127名(2022年9月時点)です。新卒採用では23卒を26人採用しておりどんどん大きくなっている会社になります。長野拠点も今年度から本格的に活動をしています!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fnagitter%2Fn%2Fn818ab466a10c" title="長野に拠点を作ります|Yuya Yanagisawa|note" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://note.com/nagitter/n/n818ab466a10c">note.com</a></cite></p> <p>そしてエンジニアという括りでいうと、社員数は32人、そのうち23卒内定者9人、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生は4人です。</p> <p>こういった背景を踏まえると、今回の交流会は</p> <ol> <li><p>長野拠点/リモートワークで、物理的に普段話せない仲間との交流の場になる点</p></li> <li><p>全国にいる23卒内定者に、弊社のエンジニア組織や雰囲気を感じてもらえる点</p></li> </ol> <p>で大きな意義があるものだと思います!</p> <p>と言っても、会社全体として危機感が募った結果、交流会をしなきゃ!となったわけではありませんでした。</p> <p>急激に人数が増えたことでコミュニケーションが疎になりつつあることを感じていて、「一回交流の場をあったら良いね」という話から、「じゃ、エンジニア交流会やろっか!」と自然にスタートしました!</p> <p>自分と仲間の「自立」に向き合える環境を創ることも弊社が大切にしていることなのです。</p> <h3 id="エンジニア交流会の内容">エンジニア交流会の内容</h3> <p>今回のエンジニア交流会はオンライン開催、3部構成でした!!</p> <blockquote><ul> <li><p>一致するまで終われまテン</p></li> <li><p>wevox <a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a> card</p></li> <li><p>Will語り会</p></li> </ul> </blockquote> <h5 id="1-一致するまで終われまテン">1. 一致するまで終われまテン</h5> <p>1つ目の<a href="https://loungegame.site/top/IOK6JQ6JUFCHTLGE4PAVDG27HU">終われまテン</a>は、zoomを使ったアイスブレイクです! 準備の段階では、オンラインだし初対面の人同士もいるしでやや心配していました。。。</p> <p>でもゲーム後には打ち解けた様子でメインルームへ帰ってきてくれました! ゲームの様子と伴に各ルームで珍回答があったようなので、一部晒しておきます! <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kekeke21/20220831/20220831224907.png" width="1200" height="869" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> 知らない観光名所で勉強になりました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kekeke21/20220901/20220901110949.png" width="888" height="541" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>「川」のつく名字で、まず西川が出てくるのか・・・</p> <h5 id="2-wevox-value-card">2. wevox <a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a> card</h5> <p>2つ目からは、自分と仲間の価値観や大切にしていることを相互理解をする催しです!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwevox.io%2Fvaluescard" title="Wevox values card" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://wevox.io/valuescard">wevox.io</a></cite></p> <p>このゲームも今回の趣旨にぴったりのもので、ゲーム中「そんなこと思ってたんだ!」とか「あ〜、どれも捨てがたいいい」などの声が終始続いていましたようです!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kekeke21/20220901/20220901104010.png" width="1200" height="693" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>終わったあとに少し雑談(いつの間にか人以外が紛れ込んでいました・・・) <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kekeke21/20220901/20220901104219.png" width="1200" height="686" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h5 id="3-Will語り会">3. Will語り会</h5> <p>場も温まってきて、自分の価値観も<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%B2%BD">言語化</a>できたところで、最後の催しです!</p> <p>できるだけ<code>①事業部が異なる、②年代が異なる</code>を意識してペアを作り、1on1形式でWill語り会をしました。</p> <blockquote><ol> <li>自分のWillとそのきっかけ</li> <li>Hajimariに入社(内定承諾)した理由</li> <li>今やっている業務内容や今後やってみたいことについて</li> </ol> </blockquote> <p>過去〜未来の時間軸で自分の価値観や想いを語ってもらいました! アンケートの結果では一番評価が高く、むしろ時間が足りなかったという声が多かったです!</p> <p>飲みに行くことが決まったメンバーもいたとか笑</p> <p>(因みに話に夢中になって写真撮るの忘れてしまいました泣)</p> <h3 id="終わりに-弊社が大切にしている自己理解と他者理解組織理解">終わりに ~弊社が大切にしている自己理解と他者理解、組織理解~</h3> <p>終わった後に、アンケートを見ながら運営陣で振り返りをしました!</p> <p>「終始笑顔が溢れていた空間だった」「内定者が打ち解けた様子で嬉しかった」「交流なので、いろんな他事業の人と話せるコンテンツでも良かったかもしれない」などなど色んな意見をいただきました!!</p> <p>ただ、アンケート上で良い評価をもらえましたが鵜呑みにするべきではありません。今回参加できなかった人との間に溝が生まれてないか、参加はしていたけど消極的だった人はいなかったか。それに、交流会などのコミュニケーションの場は1回行えば大丈夫というものではありません。かと言って1回1回が長時間となると負担が大きくなるのでそれもまた良くはないように思えます。</p> <p>反省点や考えることは尽きませんが、、、</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kekeke21/20220901/20220901110505.png" width="1200" height="617" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こういうたくさんの言葉をいただけただけでも、やって良かったなと素直に感じます!</p> <p>まだまだ組織には課題が山のようにあります(汗)が、交流会を通して気付けたこと/良くなったこともあります!</p> <p>それらに対して今後どうするか、続・エンジニア交流会をするのか、、、勉強会をやってみるのか、、、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%E2%A4%AF%A4%E2%A4%AF%B2%F1">もくもく会</a>をするのか、、、</p> <p>まぁとにかく色んなことをやってみるので今後の記事に乞うご期待ください!!</p> <p>こんな感じで(急にまとめに入る)</p> <p>以上、Hajimariのエンジニア交流会のお話でした〜!</p> <hr /> <p>株式会社Hajimariでは、一緒に働く仲間を募集しています! <br /> 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> そして気軽にお話しましょう!! <br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> kekeke21 【Raycast】心地の良いPCライフを送るためのランチャーアプリ hatenablog://entry/4207112889908591263 2022-09-12T10:00:00+09:00 2022-09-12T10:00:02+09:00 こんにちは! 6月から23卒内定者インターン生として株式会社Hajimariに入社した、江端 凌です。 普段は、TUKURÜS事業部で受託開発の業務に携わっています。 今回は普段の業務を効率化するためにインストールしたランチャーアプリであるRaycastが便利すぎたので、この場を借りてご紹介したいと思います。 ランチャーアプリとは ランチャーアプリとは、ショートカットなどの簡単操作でファイルやフォルダーの呼び出し、起動などが行えるアプリです。 Macをお持ちの方は、⌘ + spaceキーで既存のランチャーアプリである「Spotlight」が使えます。実際に、このSpotlightにアプリケーシ… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220829/20220829220350.png" alt="&#x3010;Raycast&#x3011;&#x5FC3;&#x5730;&#x306E;&#x826F;&#x3044;PC&#x30E9;&#x30A4;&#x30D5;&#x3092;&#x9001;&#x308B;&#x305F;&#x3081;&#x306E;&#x30E9;&#x30F3;&#x30C1;&#x30E3;&#x30FC;&#x30A2;&#x30D7;&#x30EA;" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!<br/> 6月から23卒内定者<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生として<a href="https://www.hajimari.inc/">株式会社Hajimari</a>に入社した、<a href="https://twitter.com/ebaryo_">江端 凌</a>です。</p> <p>普段は、TUKURÜS事業部で受託開発の業務に携わっています。</p> <p>今回は普段の業務を効率化するためにインストールしたランチャーアプリである<strong>Raycastが便利すぎた</strong>ので、この場を借りてご紹介したいと思います。</p> <h2 id="ランチャーアプリとは">ランチャーアプリとは</h2> <p>ランチャーアプリとは、ショートカットなどの簡単操作でファイルやフォルダーの呼び出し、起動などが行えるアプリです。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Mac">Mac</a>をお持ちの方は、<code>⌘ + spaceキー</code>で既存のランチャーアプリである「Spotlight」が使えます。実際に、このSpotlightにアプリケーションの名前入力すると起動してくれます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.apple.com%2Fja-jp%2Fguide%2Fmac-help%2Fmchlp1008%2Fmac" title="MacのSpotlightで検索する" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://support.apple.com/ja-jp/guide/mac-help/mchlp1008/mac">support.apple.com</a></cite></p> <p>もちろん、検索したいキーワードを入力すると<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google%20Chrome">Google Chrome</a>で検索結果を表示してくれますし、PCを使う上で痒いところに手が届くアプリなのです。</p> <h2 id="Raycastをインストールする">Raycastをインストールする</h2> <p>では、RaycastがどのようにSpotlightより優れているのかを紹介する前に、実際に使いながら実感してもらうためにインストール方法から説明していきます。</p> <p>まずはRaycastのサイトに飛んで、「<strong>Download for <a class="keyword" href="http://d.hatena.ne.jp/keyword/Mac">Mac</a></strong>」を押下してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.raycast.com%2F" title="Raycast - Supercharged productivity" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.raycast.com/">www.raycast.com</a></cite></p> <p>ダウンロードが開始され、完了すると<code>Raycast.dmg</code>がダウンロード<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ配下に入っています。</p> <p>クリックして開くと以下のような画面になっていると思うので、</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220818/20220818160653.png" alt="Raycast&#x306E;&#x30A4;&#x30F3;&#x30B9;&#x30C8;&#x30FC;&#x30EB;&#x753B;&#x9762;" width="1200" height="674" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>左側のRaycastのアイコンをドラッグして、右側のApplications<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リにドロップします。これで<a class="keyword" href="http://d.hatena.ne.jp/keyword/Mac">Mac</a>のLaunchpadなどからRaycastを探せるようになります。</p> <p>Raycastを起動したら以下のような表示になります(初回起動は確認事項があるので、全て右下のボタンをクリックでOKです)。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220818/20220818162021.png" alt="Raycast&#x306E;&#x521D;&#x671F;&#x753B;&#x9762;" width="1200" height="816" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="Raycastのここがすごい">Raycastのここがすごい</h2> <p>個人的にRaycastがすごいと思う箇所は、その<strong>万能さ</strong>です。</p> <p>例として、コピーした履歴を表示させる「Clipy」という有名なアプリがありますが、似たようなことがRaycastの「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」という機能でも可能です。<br/> もちろん「Clipy」とは使い勝手も違うので、この場でどちらが便利だよというわけではありませんが、よく使われるアプリの機能も包括的にサポートしているという点がRaycastの大きなメリットです。</p> <h3 id="Raycastの基本機能">Raycastの基本機能</h3> <p>ではどのように「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」を利用するかご紹介していきます。</p> <p>まずは<code>option + spaceキー</code>でRaycastを起動して、検索窓に「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」と入力します。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220829/20220829143818.png" alt="ClipboardHistory" width="1200" height="816" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>すると、一番上に「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」が表示されているので、選択してください。<br/> そうするとコピー履歴が表示されます。</p> <p>ただ、呼び出すのが面倒ですよね。 Raycastにはわざわざ<code>option + spaceキー</code>で起動して、検索欄で検索をかけなくても良い方法がありますが、それは後述します。</p> <p>前述した通り、Raycastのすごいところはその万能さです。<br/> Raycastは「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」以外に、多くの機能を備えています。</p> <h4 id="Window-Management">Window Management</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220829/20220829152608.png" alt="WindowManagement" width="1200" height="816" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>「<strong>Window Management</strong>」は、ウインドウサイズを自在に操作できる機能です。<br/> 主に左右に画面分割するときに重宝します。</p> <h4 id="Snippets">Snippets</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220829/20220829152843.png" alt="Snippets" width="1200" height="816" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>「<strong>Create Snippet</strong>」、「<strong>Search Snippet</strong>」、「<strong>Import Snippet</strong>」は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%CB%A5%DA%A5%C3%A5%C8">スニペット</a>を作成、検索できる機能です。<br/> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%CB%A5%DA%A5%C3%A5%C8">スニペット</a>に登録した内容は、「<strong>Keyword</strong>」を入力したら呼び出せます。</p> <h4 id="Quit-All-Applications">Quit All Applications</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220829/20220829153552.png" alt="QuitAllApplications" width="1200" height="790" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>全アプリケーションを落とせます。<br/> PCを使い続けていると、いろんなアプリを開きまくって負荷もかけまくっちゃいがち。<br/> このコマンドで定期的にアプリを落としてあげましょう。</p> <h3 id="ショートカット">ショートカット</h3> <p>では、そんな便利なコマンドたちをショートカットで呼び出せるようにしていきましょう。</p> <p>Raycastには「<strong>Hotkey</strong>」と呼ばれる機能があり、これを使うことでRaycastの機能をショートカットに登録できます。</p> <p>例として、僕は<code>shift + control + H</code>に「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」登録しています。</p> <p>登録の方法としては、</p> <ol> <li><code>option + spaceキー</code>でRaycastを起動。</li> <li><code>⌘ + ,(カンマ)</code>で設定画面を表示。</li> <li>Extensions から、「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」を検索。</li> <li>「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」のHotkeyの欄をクリックして、画像のように「<strong>Recording...</strong>」と表示されたら、登録したいキーを押してください。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220829/20220829151354.png" alt="ExtensionsHotkey" width="1200" height="790" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></li> <li>一度<code>option + spaceキー</code>でRaycastを閉じて、登録したショートカットで「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」が起動したら成功です。</li> </ol> <p>こうすることで、Raycastを開かなくても使いたい機能を使うことができます。<br/> ちなみに、Raycastの他の機能も同様に登録することが可能です。</p> <p>※ おすすめは<code>shift + control + ⇦ | ⇨</code>に「<strong>Window Management</strong>」の左半分(Left Half) と右半分(Right Half)。</p> <p>これでRaycastの体験が爆上がりします。</p> <h3 id="Raycastの拡張機能">Raycastの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a></h3> <p>ここまでご紹介した、「<strong>Window Management</strong>」や「<strong>Clipboard <a class="keyword" href="http://d.hatena.ne.jp/keyword/History">History</a></strong>」はRaycast以外でも賄えてしまいます。<br/> 全部Raycastで済むというメリットはありますが、同じようなランチャーアプリである「Alfred」でも可能です。</p> <p><div class="itunes-embed freezed itunes-kind-mac-software"><a href="https://apps.apple.com/jp/app/alfred/id405843582?mt=12&uo=4&at=10l8JW&ct=hatenablog" rel="nofollow" target="_blank"><img src="https://cdn.image.st-hatena.com/image/scale/1593fa2e5ae37f8608043e118fa13d20756a4242/enlarge=0;height=200;version=1;width=200/https%3A%2F%2Fis4-ssl.mzstatic.com%2Fimage%2Fthumb%2FPurple%2Fv4%2Fb4%2F7b%2F51%2Fb47b5118-5ba5-a359-0bc6-e71494225963%2Fappicon.png%2F100x100bb.png" alt="Alfred" title="Alfred" class="itunes-embed-image"/></a><div class="itunes-embed-info"><p class="itunes-embed-title"><a href="https://apps.apple.com/jp/app/alfred/id405843582?mt=12&uo=4&at=10l8JW&ct=hatenablog" rel="nofollow" target="_blank">Alfred</a></p><ul><li class="itunes-embed-artist">Running with Crayons Ltd</li><li class="itunes-embed-genre">仕事効率化</li><li class="itunes-embed-price">無料</li><li class="itunes-embed-badge"><a href="https://apps.apple.com/jp/app/alfred/id405843582?mt=12&uo=4&at=10l8JW&ct=hatenablog" rel="nofollow" target="_blank"><img src="https://cdn.blog.st-hatena.com/images/theme/itunes/itunes-badge-macappstore@2x.png" width="80px" height="15px" /></a></li></ul></div></div><cite class="hatena-citation"><a href="https://apps.apple.com/jp/app/alfred/id405843582?mt=12">apps.apple.com</a></cite></p> <p>ですが、RaycastとAlfledでは「<strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a></strong>」が大きく違います。<br/> 既存のランチャーアプリとしての機能を超えて、便利なアプリが多くリリースされているのですが、Raycastでは基本的に無料で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>が提供されており、対するAlfredは有料です。</p> <p>では、どんな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>がRaycastにあるのかを少しだけご紹介します。</p> <h4 id="Brew"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Brew">Brew</a></h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.raycast.com%2Fnhojb%2Fbrew" title="Brew" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.raycast.com/nhojb/brew">www.raycast.com</a></cite></p> <p>HomebrewをRaycastで操作できます。<br/> 何がインストールされているか確認できたり、アップグレードをすることができます。</p> <h4 id="Github"><a class="keyword" href="http://d.hatena.ne.jp/keyword/Github">Github</a></h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.raycast.com%2Fraycast%2Fgithub" title="GitHub" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.raycast.com/raycast/github">www.raycast.com</a></cite></p> <p>Raycastで<strong>Issueの作成やPRの作成</strong>が行えます。<br/> ワークフローの実行まで可能なので、サクッとやりたいときに便利。</p> <h4 id="Docker">Docker</h4> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.raycast.com%2Fpriithaamer%2Fdocker" title="Docker" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.raycast.com/priithaamer/docker">www.raycast.com</a></cite></p> <p>Dockerのコンテナを起動・停止できたり、Imageの確認・削除もできます。<br/> 自分はDocker for Desktopの挙動がたまにおかしくなるので、重宝しています。</p> <p>その他Raycastの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B3%C8%C4%A5%B5%A1%C7%BD">拡張機能</a>は、Raycast内の「<strong>Store</strong>」から探すことができるので、気になったものを探してみるもよし、なんなら作っちゃうのもありですね。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220829/20220829160931.png" alt="Store" width="1200" height="816" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fzenn.dev%2Fryo_kawamata%2Farticles%2F965ef95ad8bb0d" title="esa の記事を簡単に検索できる Raycast 拡張を作ってみた" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://zenn.dev/ryo_kawamata/articles/965ef95ad8bb0d">zenn.dev</a></cite></p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F1086721" title="24卒エンジニア職|就活が本格化する前にカジュアルにお話ししませんか? by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/1086721">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> ryo_eba 【PHP】Carbon::now()は誰のnow? hatenablog://entry/4207112889912024242 2022-09-05T10:00:00+09:00 2022-09-05T10:00:03+09:00 carbonで取ってくるnowはどこのnowなのでしょうか?そしてその情報はどういった仕組みで取得されるのでしょうか? <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220826/20220826160018.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは、毎日暑いですね。エンジニアの<a href="https://twitter.com/miyakey7">三宅</a>です。</p> <p>最近、仕事で<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>のコードリーディングを少ししたので、その過程を記載します!</p> <ul class="table-of-contents"> <li><a href="#CarbonnowはOSの現在のシステム時刻">Carbon::now()はOSの現在のシステム時刻</a><ul> <li><a href="#Carbonとは">Carbonとは</a></li> <li><a href="#PHPの時間操作">PHPの時間操作</a></li> <li><a href="#コードリーディング方針">コードリーディング方針</a></li> </ul> </li> <li><a href="#WEBでコードリーディング実践">WEBでコードリーディング実践</a><ul> <li><a href="#Carbonクラスのコードリーディング">Carbonクラスのコードリーディング</a></li> <li><a href="#DateTimeクラスのコードリーディング">DateTimeクラスのコードリーディング</a><ul> <li><a href="#ちなみにWindowsのようなOSの場合はどうなるのか">ちなみにWindowsのようなOSの場合はどうなるのか??</a></li> </ul> </li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h1 id="CarbonnowはOSの現在のシステム時刻">Carbon::now()はOSの現在のシステム時刻</h1> <p>結論はこれです。<code>OSのシステム時刻</code>を持ってきています。</p> <p>これ以降の内容は、コードを見ながら本当に<code>システム時刻</code>から取ってきているのかを確認するためのものです。</p> <h2 id="Carbonとは">Carbonとは</h2> <p>今回扱っているCarbonは<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>のDateTimeクラスを継承し、時間操作を簡易化したり便利化したものを提供する拡張クラスになります!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcarbon.nesbot.com%2F" title="Carbon - A simple PHP API extension for DateTime." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://carbon.nesbot.com/">carbon.nesbot.com</a></cite></p> <p>これが所謂<code>Carbon::now()</code>の使用例</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synPreProc">require</span> <span class="synConstant">'vendor/autoload.php'</span>; <span class="synPreProc">use</span> Carbon\Carbon; <span class="synStatement">$</span><span class="synIdentifier">now</span> <span class="synStatement">=</span> Carbon<span class="synStatement">::</span>now<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">now</span><span class="synType">-&gt;</span>format<span class="synSpecial">(</span><span class="synConstant">'Y-m-d H:i:s'</span><span class="synSpecial">)</span>; <span class="synComment">// 2022-08-24 19:31:22</span> </pre> <h2 id="PHPの時間操作"><a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の時間操作</h2> <p>DateTimeは<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>標準クラスです!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.php.net%2Fmanual%2Fja%2Fclass.datetime.php" title="PHP: DateTime - Manual" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.php.net/manual/ja/class.datetime.php">www.php.net</a></cite></p> <p>Carbonはこれを継承しています。</p> <h2 id="コードリーディング方針">コードリーディング方針</h2> <p>今回は如何にWEB完結でコードリーディングしていくかの手順を書いていきますが、本格的に処理を追うのであれば debug実行出来る環境を手元に用意すると良いと思います。</p> <h1 id="WEBでコードリーディング実践">WEBでコードリーディング実践</h1> <h2 id="Carbonクラスのコードリーディング">Carbonクラスのコードリーディング</h2> <p>では早速Carbonから追っていきましょう</p> <p>まずはCarbonの<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>から<code>Carbon::now();</code>の処理を確認します。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fbriannesbitt%2FCarbon" title="GitHub - briannesbitt/Carbon: A simple PHP API extension for DateTime." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/briannesbitt/Carbon">github.com</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>上で検索する際には"function now"とダブルクォーテーションで括ります こうすると目指す関数に当たりやすいです!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220826/20220826192357.png" width="745" height="747" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220826/20220826192402.png" width="932" height="334" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ちなみに対象関数を探すのは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>上よりもgit cloneしてローカルファイルをエディターで検索した方が楽です</p> <p>ただ、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>検索するとissueやcommitメッセージなども対象になるため色んな情報を得る可能性があるというメリットもあります</p> <p>検索の結果、どうやらここにあるようです!! <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fbriannesbitt%2FCarbon%2Fblob%2Fmaster%2Fsrc%2FCarbon%2FTraits%2FCreator.php" title="Carbon/Creator.php at master · briannesbitt/Carbon" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/briannesbitt/Carbon/blob/master/src/Carbon/Traits/Creator.php">github.com</a></cite></p> <p>nowメソッド</p> <pre class="code lang-php" data-lang="php" data-unlink>/** * Get a Carbon instance for the current date and time. * * <span class="synPreProc">@param </span>DateTimeZone|string|null $tz * * <span class="synPreProc">@return </span>static */ public static function now($tz = null) { return new static(null, $tz); // ← ここでインスタンス生成して返している } </pre> <p>newしたものを返しているようです! <code>new static</code>は実行されるクラスの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を生成します。</p> <p>実行クラスのコンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タが第一引数nullで呼ばれますので、そちらの処理を確認</p> <pre class="code lang-php" data-lang="php" data-unlink>public function <span class="synStatement">__construct</span>($time = null, $tz = null) { if ($time instanceof DateTimeInterface) { $time = $this-<span class="synError">&gt;</span>constructTimezoneFromDateTime($time, $tz)-<span class="synError">&gt;</span>format('Y-m-d H:i:s.u'); } if (is_numeric($time) <span class="synError">&amp;&amp;</span> (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) { $time = static::createFromTimestampUTC($time)-<span class="synError">&gt;</span>format('Y-m-d\TH:i:s.uP'); } // If the class has a test now set and we are trying to create a now() // instance then override as required $isNow = <span class="synStatement">empty</span>($time) || $time === 'now'; if (method_exists(static::class, 'hasTestNow') <span class="synError">&amp;&amp;</span> method_exists(static::class, 'getTestNow') <span class="synError">&amp;&amp;</span> static::hasTestNow() <span class="synError">&amp;&amp;</span> ($isNow || static::hasRelativeKeywords($time)) ) { static::mockConstructorParameters($time, $tz); } // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127 if (!str_contains((string) .1, '.')) { $locale = setlocale(LC_NUMERIC, '0'); setlocale(LC_NUMERIC, 'C'); } try { // ここで親コンストラクタを呼んでいる!!!!!! parent::<span class="synStatement">__construct</span>($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); } catch (<span class="synIdentifier">Exception</span> $<span class="synIdentifier">exception</span>) { throw new InvalidFormatException($<span class="synIdentifier">exception</span>-<span class="synError">&gt;</span>getMessage(), 0, $<span class="synIdentifier">exception</span>); } $this-<span class="synError">&gt;</span>constructedObjectId = spl_object_hash($this); if (<span class="synStatement">isset</span>($locale)) { setlocale(LC_NUMERIC, $locale); } self::setLastErrors(parent::getLastErrors()); } </pre> <p>親のコンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タを呼んでいます。</p> <p><code>parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); *</code></p> <p>Carbonの親クラスは前述通りDateTimeクラスになります。 そもそも元のコンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タの第一引数である$timeはnullできているので、このCarbon→DateTimeのコンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タへ移行するタイミングで ’now’という引数が設定されることが読み取れます!</p> <p>そうすると今度はDateTimeクラスになっていきます</p> <h2 id="DateTimeクラスのコードリーディング">DateTimeクラスのコードリーディング</h2> <p>DateTimeクラスのコンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タには説明ドキュメントがありました</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.php.net%2Fmanual%2Fja%2Fdatetime.construct.php" title="PHP: DateTime::__construct - Manual" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.php.net/manual/ja/datetime.construct.php">www.php.net</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の元コードは<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>で書かれています!</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>で開発はされていないのですが、ミラーされたコードが上がっています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fphp%2Fphp-src" title="GitHub - php/php-src: The PHP Interpreter" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/php/php-src">github.com</a></cite></p> <p>まずは"class DateTime"で検索してみます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220827/20220827115652.png" width="796" height="592" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>ではなく<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>のstabファイルで引っ掛かりました。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220827/20220827115703.png" width="880" height="211" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <pre class="code lang-php" data-lang="php" data-unlink>/** * Representation of date and time. * <span class="synPreProc">@link </span>https://php.net/manual/en/class.datetime.php */ class DateTime implements DateTimeInterface { ... } </pre> <p>ここでファイルの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに注目!! dateを扱ってそうな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リです。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220827/20220827115709.png" width="695" height="70" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a>_date.cが怪しいなといって見ていきます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220827/20220827115721.png" width="540" height="563" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>今度はブラウザ検索で"<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>_FUNCTION"を見ていきます。 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20220827/20220827121530.png" width="894" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p><code>PHP_FUNCTION</code>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>のマクロで定義されています。 機能は引数でもらった名前の関数を<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>で動作させるものです。</p> <p><code>DateTime class</code>のコンスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タに当たりそうな処理が見つかりました!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fphp%2Fphp-src%2Fblob%2Feff9aed1592f59cddb12d36a55dec0ccc3bbbfd6%2Fext%2Fdate%2Fphp_date.c%23L2397" title="php-src/php_date.c at eff9aed1592f59cddb12d36a55dec0ccc3bbbfd6 · php/php-src" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/php/php-src/blob/eff9aed1592f59cddb12d36a55dec0ccc3bbbfd6/ext/date/php_date.c#L2397">github.com</a></cite></p> <p>コメントにReturns new DateTime objectとありますね!</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synComment">/* {{{ Returns new DateTime object */</span> <span class="synIdentifier">PHP_FUNCTION</span>(date_create) { zval *timezone_object = <span class="synConstant">NULL</span>; <span class="synType">char</span> *time_str = <span class="synConstant">NULL</span>; <span class="synType">size_t</span> time_str_len = <span class="synConstant">0</span>; <span class="synIdentifier">ZEND_PARSE_PARAMETERS_START</span>(<span class="synConstant">0</span>, <span class="synConstant">2</span>) Z_PARAM_OPTIONAL <span class="synIdentifier">Z_PARAM_STRING</span>(time_str, time_str_len) <span class="synIdentifier">Z_PARAM_OBJECT_OF_CLASS_OR_NULL</span>(timezone_object, date_ce_timezone) <span class="synIdentifier">ZEND_PARSE_PARAMETERS_END</span>(); <span class="synIdentifier">php_date_instantiate</span>(date_ce_date, return_value); <span class="synStatement">if</span> (!<span class="synIdentifier">php_date_initialize</span>(<span class="synIdentifier">Z_PHPDATE_P</span>(return_value), time_str, time_str_len, <span class="synConstant">NULL</span>, timezone_object, <span class="synConstant">0</span>)) { <span class="synIdentifier">zval_ptr_dtor</span>(return_value); RETURN_FALSE; } } <span class="synComment">/* }}} */</span> </pre> <p>上記コードは<code>date_create()</code>という<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>関数の実行処理内容です!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.php.net%2Fmanual%2Fja%2Ffunction.date-create.php" title="PHP: date_create - Manual" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.php.net/manual/ja/function.date-create.php">www.php.net</a></cite></p> <p><code>date_create()</code>を知っていれば、最初から<code>date_create</code>で検索すればよかったのでは?と思うかと思います。</p> <p><strong>その通りです!</strong></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>関数名が分かっていればストレートに検索していけば問題なくたどり着けます!</p> <p>さて、<code>PHP_FUNCTION(date_create)</code>の中を見てきます。。 <code>php_date_initialize</code>関数が正にその実行処理のようです! コードを追っていきます。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink>PHPAPI <span class="synType">bool</span> <span class="synIdentifier">php_date_initialize</span>(php_date_obj *dateobj, <span class="synType">const</span> <span class="synType">char</span> *time_str, <span class="synType">size_t</span> time_str_len, <span class="synType">const</span> <span class="synType">char</span> *<span class="synType">format</span>, zval *timezone_object, <span class="synType">int</span> flags) <span class="synComment">/* {{{ */</span> { timelib_time *now; timelib_tzinfo *tzi = <span class="synConstant">NULL</span>; timelib_error_container *err = <span class="synConstant">NULL</span>; <span class="synType">int</span> type = TIMELIB_ZONETYPE_ID, new_dst = <span class="synConstant">0</span>; <span class="synType">char</span> *new_abbr = <span class="synConstant">NULL</span>; timelib_sll new_offset = <span class="synConstant">0</span>; <span class="synType">time_t</span> sec; suseconds_t usec; <span class="synType">int</span> options = <span class="synConstant">0</span>; <span class="synStatement">if</span> (dateobj-&gt;time) { <span class="synIdentifier">timelib_time_dtor</span>(dateobj-&gt;time); } <span class="synStatement">if</span> (<span class="synType">format</span>) { <span class="synStatement">if</span> (time_str_len == <span class="synConstant">0</span>) { time_str = <span class="synConstant">&quot;&quot;</span>; } dateobj-&gt;time = <span class="synIdentifier">timelib_parse_from_format</span>(<span class="synType">format</span>, time_str, time_str_len, &amp;err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper); } <span class="synStatement">else</span> { <span class="synStatement">if</span> (time_str_len == <span class="synConstant">0</span>) { time_str = <span class="synConstant">&quot;now&quot;</span>; time_str_len = <span class="synStatement">sizeof</span>(<span class="synConstant">&quot;now&quot;</span>) - <span class="synConstant">1</span>; } dateobj-&gt;time = <span class="synIdentifier">timelib_strtotime</span>(time_str, time_str_len, &amp;err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper); } </pre> <p>んー、まだnowの時間を取ってはなさそうですね..!</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink> <span class="synComment">/* update last errors and warnings */</span> <span class="synIdentifier">update_errors_warnings</span>(err); <span class="synComment">/* If called from a constructor throw an exception */</span> <span class="synStatement">if</span> ((flags &amp; PHP_DATE_INIT_CTOR) &amp;&amp; err &amp;&amp; err-&gt;error_count) { <span class="synComment">/* spit out the first library error message, at least */</span> <span class="synIdentifier">zend_throw_exception_ex</span>(<span class="synConstant">NULL</span>, <span class="synConstant">0</span>, <span class="synConstant">&quot;Failed to parse time string (</span><span class="synSpecial">%s</span><span class="synConstant">) at position </span><span class="synSpecial">%d</span><span class="synConstant"> (</span><span class="synSpecial">%c</span><span class="synConstant">): </span><span class="synSpecial">%s</span><span class="synConstant">&quot;</span>, time_str, err-&gt;error_messages[<span class="synConstant">0</span>].position, err-&gt;error_messages[<span class="synConstant">0</span>].character, err-&gt;error_messages[<span class="synConstant">0</span>].message); } <span class="synStatement">if</span> (err &amp;&amp; err-&gt;error_count) { <span class="synIdentifier">timelib_time_dtor</span>(dateobj-&gt;time); dateobj-&gt;time = <span class="synConstant">0</span>; <span class="synStatement">return</span> <span class="synConstant">0</span>; } <span class="synStatement">if</span> (timezone_object) { php_timezone_obj *tzobj; tzobj = <span class="synIdentifier">Z_PHPTIMEZONE_P</span>(timezone_object); <span class="synStatement">switch</span> (tzobj-&gt;type) { <span class="synStatement">case</span> TIMELIB_ZONETYPE_ID: tzi = tzobj-&gt;tzi.tz; <span class="synStatement">break</span>; <span class="synStatement">case</span> TIMELIB_ZONETYPE_OFFSET: new_offset = tzobj-&gt;tzi.utc_offset; <span class="synStatement">break</span>; <span class="synStatement">case</span> TIMELIB_ZONETYPE_ABBR: new_offset = tzobj-&gt;tzi.z.utc_offset; new_dst = tzobj-&gt;tzi.z.dst; new_abbr = <span class="synIdentifier">timelib_strdup</span>(tzobj-&gt;tzi.z.abbr); <span class="synStatement">break</span>; } type = tzobj-&gt;type; } <span class="synStatement">else</span> <span class="synStatement">if</span> (dateobj-&gt;time-&gt;tz_info) { tzi = dateobj-&gt;time-&gt;tz_info; } <span class="synStatement">else</span> { tzi = <span class="synIdentifier">get_timezone_info</span>(); <span class="synStatement">if</span> (!tzi) { <span class="synStatement">return</span> <span class="synConstant">0</span>; } } now = <span class="synIdentifier">timelib_time_ctor</span>(); now-&gt;zone_type = type; <span class="synStatement">switch</span> (type) { <span class="synStatement">case</span> TIMELIB_ZONETYPE_ID: now-&gt;tz_info = tzi; <span class="synStatement">break</span>; <span class="synStatement">case</span> TIMELIB_ZONETYPE_OFFSET: now-&gt;z = new_offset; <span class="synStatement">break</span>; <span class="synStatement">case</span> TIMELIB_ZONETYPE_ABBR: now-&gt;z = new_offset; now-&gt;dst = new_dst; now-&gt;tz_abbr = new_abbr; <span class="synStatement">break</span>; } </pre> <p>timezone関連の処理が続いてますね。。。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink> <span class="synIdentifier">php_date_get_current_time_with_fraction</span>(&amp;sec, &amp;usec); <span class="synIdentifier">timelib_unixtime2local</span>(now, (timelib_sll) sec); <span class="synIdentifier">php_date_set_time_fraction</span>(now, usec); </pre> <p>きた!</p> <p>遂に見つけました...!!</p> <p><code>php_date_get_current_time_with_fraction</code></p> <p>正にというような名前ですね。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">static</span> <span class="synType">void</span> <span class="synIdentifier">php_date_get_current_time_with_fraction</span>(<span class="synType">time_t</span> *sec, suseconds_t *usec) { <span class="synPreProc">#if HAVE_GETTIMEOFDAY</span> <span class="synType">struct</span> timeval tp = {<span class="synConstant">0</span>}; <span class="synComment">/* For setting microseconds */</span> <span class="synIdentifier">gettimeofday</span>(&amp;tp, <span class="synConstant">NULL</span>); *sec = tp.tv_sec; *usec = tp.tv_usec; <span class="synPreProc">#else</span> *sec = <span class="synIdentifier">time</span>(<span class="synConstant">NULL</span>); *usec = <span class="synConstant">0</span>; <span class="synPreProc">#endif</span> } </pre> <p><code>#if HAVE_GETTIMEOFDAY</code>この記述は<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>の<code>ifdef</code>機能で、条件<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%E9">コンパイラ</a>のための機能です。 <code>HAVE_GETTIMEOFDAY</code>を<code>0</code> or <code>1</code>で定義することで、どちらのコードが取り込まれるかが決まります。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>の現在時間取得メソッドである <code>gettimeofday(&amp;tp, NULL);</code>や<code>time(NULL);</code> 処理があります。</p> <p>gettimeofday は <a class="keyword" href="http://d.hatena.ne.jp/keyword/POSIX">POSIX</a> に準拠した関数で、マイクロ秒単位の精度で現在の時刻を取得します。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Flinuxjm.osdn.jp%2Fhtml%2FLDP_man-pages%2Fman2%2Fgettimeofday.2.html" title="Man page of GETTIMEOFDAY" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://linuxjm.osdn.jp/html/LDP_man-pages/man2/gettimeofday.2.html">linuxjm.osdn.jp</a></cite></p> <p>timeは<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>の時間ライブラリの関数です。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.geeksforgeeks.org%2Ftime-function-in-c%2F" title="time() function in C - GeeksforGeeks" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.geeksforgeeks.org/time-function-in-c/">www.geeksforgeeks.org</a></cite></p> <h3 id="ちなみにWindowsのようなOSの場合はどうなるのか">ちなみに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>のようなOSの場合はどうなるのか??</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>環境では<code>HAVE_GETTIMEOFDAY</code>が<code>1</code>です <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fphp%2Fphp-src%2Fblob%2F5171cb435a69c418231acc487819ce47b52711df%2Fwin32%2Fbuild%2Fconfig.w32.h.in%23L59" title="php-src/config.w32.h.in at 5171cb435a69c418231acc487819ce47b52711df · php/php-src" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/php/php-src/blob/5171cb435a69c418231acc487819ce47b52711df/win32/build/config.w32.h.in#L59">github.com</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>環境でのgettimeofdayはここにある処理が走ります <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fphp%2Fphp-src%2Fblob%2F5b01c4863fe9e4bc2702b2bbf66d292d23001a18%2Fwin32%2Ftime.c%23L81" title="php-src/time.c at 5b01c4863fe9e4bc2702b2bbf66d292d23001a18 · php/php-src" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/php/php-src/blob/5b01c4863fe9e4bc2702b2bbf66d292d23001a18/win32/time.c#L81">github.com</a></cite></p> <p>結果としてここのコードで<a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a> OSの時間を取得しています <a href="https://github.com/php/php-src/blob/5b01c4863fe9e4bc2702b2bbf66d292d23001a18/win32/time.c#L31">https://github.com/php/php-src/blob/5b01c4863fe9e4bc2702b2bbf66d292d23001a18/win32/time.c#L31</a></p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">static</span> zend_always_inline MyGetSystemTimeAsFileTime <span class="synIdentifier">get_time_func</span>(<span class="synType">void</span>) {<span class="synComment">/*{{{*/</span> MyGetSystemTimeAsFileTime timefunc = <span class="synConstant">NULL</span>; HMODULE hMod = <span class="synIdentifier">GetModuleHandle</span>(<span class="synConstant">&quot;kernel32.dll&quot;</span>); <span class="synStatement">if</span> (hMod) { <span class="synComment">/* Max possible resolution &lt;1us, win8/server2012 */</span> timefunc = (MyGetSystemTimeAsFileTime)<span class="synIdentifier">GetProcAddress</span>(hMod, <span class="synConstant">&quot;GetSystemTimePreciseAsFileTime&quot;</span>); } <span class="synStatement">if</span>(!timefunc) { <span class="synComment">/* 100ns blocks since 01-Jan-1641 */</span> timefunc = (MyGetSystemTimeAsFileTime) GetSystemTimeAsFileTime; } <span class="synStatement">return</span> timefunc; }<span class="synComment">/*}}}*/</span> </pre> <p><code>GetSystemTimePreciseAsFileTime</code>か<code>GetSystemTimeAsFileTime</code>という関数が<a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>の関数ですね。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows8">Windows8</a>以降でより高精度の時間を取得する関数が増えたために分岐があるようです。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmetatrading.hatenablog.com%2Fentry%2F2017%2F06%2F19%2F003513" title="Windows環境における高精度時刻取得について - metatrading’s diary" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://metatrading.hatenablog.com/entry/2017/06/19/003513#GetSystemTimePreciseAsFileTime%E9%96%A2%E6%95%B0%E3%81%AB%E3%82%88%E3%82%8B%E8%A8%88%E6%B8%AC%E7%B5%90%E6%9E%9C">metatrading.hatenablog.com</a></cite></p> <h1 id="まとめ">まとめ</h1> <p>ということで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>でCarbon::nowをコールすると<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>内部の<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>でOSのシステム時刻を取得しています!</p> <p><code>Carbon::now(Carbonクラス) → DateTime(PHP) →gettimeofday/ time (C言語)→ OS ...</code></p> <p>OSがどの様に時刻を刻んでいるか、その仕組みなど興味があれば色々調べてみると面白いです。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fja.wikipedia.org%2Fwiki%2F%25E3%2582%25B7%25E3%2582%25B9%25E3%2583%2586%25E3%2583%25A0%25E6%2599%2582%25E5%2588%25BB" title="システム時刻 - Wikipedia" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%99%82%E5%88%BB">ja.wikipedia.org</a></cite></p> <p>何気なく使っている関数の裏側にはどんな処理が走っているか、WEB上や自分の環境で確認できる手段を持つことは理解を深める一手になるのではないかと思います。</p> kumamon_engineer 【テックブログ】新卒エンジニアが入社2ヶ月で新規サービスをリリースした話。 hatenablog://entry/4207112889905204134 2022-08-09T13:00:00+09:00 2022-08-09T13:00:01+09:00 こんにちは! 株式会社Hajimari22卒エンジニアの神野 凌太郎です。 普段は、事業部づけのエンジニアとして人事プロパートナーズの開発業務を担当しています。 内定者インターン〜新卒入社後の期間、人事プロパートナーズの開発と並行して 新規サービス「アミーチ」の開発・立ち上げを1人で行いました。 今回は、実際に新規事業の開発・立ち上げを行なって、学んだこと・反省点をまとめてお伝えできればと思います! ■新規サービス「アミーチ」概要 『人事専門型の求人サイト』として立ち上げたサービスです。 もともと、人事プロパートナーズ内で人材紹介を行なっており、 その専用サイトを立ち上げようというところからス… <p>こんにちは! 株式会社Hajimari22卒エンジニアの<a href="https://twitter.com/ryotaro00963">神野 凌太郎</a>です。</p> <p>普段は、事業部づけのエンジニアとして<a href="https://itpropartners.com/hr/">人事プロパートナーズ</a>の開発業務を担当しています。 内定者<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>〜新卒入社後の期間、人事プロパートナーズの開発と並行して 新規サービス「アミーチ」の開発・立ち上げを1人で行いました。</p> <p>今回は、実際に新規事業の開発・立ち上げを行なって、学んだこと・反省点をまとめてお伝えできればと思います!</p> <h2 id="新規サービスアミーチ概要">■新規サービス「アミーチ」概要</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20220802/20220802195812.png" width="1200" height="300" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>『人事専門型の求人サイト』として立ち上げたサービスです。 もともと、人事プロパートナーズ内で人材紹介を行なっており、 その専用サイトを立ち上げようというところからスタートしています。</p> <p>人事を扱う求人サイトだからこそ、学校の保健室のように、 気軽にキャリアを考えられる、そんな求人サイトを作りたいという思いから、 「アミーチ」の立ち上げがスタートしました。</p> <p>現在、リリースから約2ヶ月たち、 ユーザー数、求人数ともに右肩上がりで増えていますので、 正社員人事を採用したい企業様、人事職にチャレンジしたい、キャリアアップをしたいとお考えの求職者様、ぜひご登録をお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbiz.ami-chi.com%2F" title="アミーチ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://biz.ami-chi.com/">biz.ami-chi.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fami-chi.com%2F" title="人事専門の転職サイト「アミーチ」|人事の案件多数掲載" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://ami-chi.com/">ami-chi.com</a></cite></p> <h2 id="開発に着手するまで">■開発に着手するまで</h2> <p>開発をするにあたって、まずざっくりとした要件定義を行いました。 後述しますが、<strong>当時は何よりも早く開発することを優先</strong>していたため、 開発の段階では「ざっくりと要件を詰めれば良い」という認識でいました。</p> <p>サイトに関しては、先に求人を募るために TO B → TO Cサイトの順でリリースを目指すことになりました。</p> <p>具体的なスケジュールとしては、 TO B向けサイトは5月末、TO C向けサイトは6月中旬のリリースを目指しました。</p> <p>技術選定は、Laravelとvue.jsを利用し、デザイン部分はbootstrapを利用することにしました。</p> <p>サービスの立ち上げ・開発自体が初めての経験であったかつ、 1人で開発を行う(LPは外注しました)という状況だったので、 不安を抱えながら開発をすることになりました。</p> <h2 id="開発過程リリースに至るまで">■開発過程〜リリースに至るまで</h2> <p>実際、TO B向けサイトは5月末、TO C向けサイトは6月中旬と スケジュール通りにリリースまで持っていくことができました。 しかし、リリースに至るまでいくつものハードルがありました。</p> <h3 id="要件が途中でいくつも変更があったこと">要件が途中でいくつも変更があったこと</h3> <p>最も大変だったハードルは、<strong>「途中で要件が変わること」</strong>でした。</p> <p>もともとフワッと始まったプロジェクトだったので、 要件をガチガチに決めていなかったこともあり、ビジネスサイドとの認識齟齬がある状態で 開発を進めてしまったためです。</p> <p>特に、リリース直前にいくつか改善案や意見をもらったときは、 「本当にリリースできるのか?」と思ったくらい絶望的な状態でした。</p> <p>ただ、もともとひいていたスケジュールより、 前倒しして開発を進めていたので、優先順位をつけて対応することで リリース時期がズレることなく、リリースすることができました。</p> <p>学んだこととしては、</p> <ul> <li>時間がかかっても、要件定義はガチガチに固めてから開発をすること</li> <li>キャパを越えそうなときは、優先順位を目に見える形で洗い出してから対応すること</li> </ul> <p>です。</p> <p>要件定義は、開発スピードを優先していたとしても、 きちんと行なったほうがスムーズに開発できることを身を持って知りました。(当たり前のことかもしれませんが) 最低でも、「テーブル定義」「機能設計」「画面設計」「<a class="keyword" href="http://d.hatena.ne.jp/keyword/URI">URI</a>設計」「モデル図」くらいは行なったほうが良いと思います。 アウトプットをあらかじめ行なっておくことで、プロジェクトメンバーとコンセンサスが取れている状態で認識齟齬なく開発を行うことができます。</p> <h3 id="メインサービス人事プロとの開発両立">メインサービス「人事プロ」との開発両立</h3> <p>2つ目は、「サービス開発の両立」です。 弊事業部では、エンジニア2人チームで行っているため、 新規サービスの開発だけに専念できるわけではありません。</p> <p>別で、先輩エンジニアが大規模の新規サービス開発を行なっていたため、 そちらの開発にかかっきりな状態ということもあり、 メインサービスの「人事プロ」の開発業務も兼任していました。</p> <p>新規サービスを立ち上げをするにあたって、 大前提メインの事業の収支を成り立たせないといけません。</p> <p>また事業自体が少数精鋭で運営しているので、 エンジニア観点から常に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%D1%A5%AF">インパク</a>トのある改善、開発を行なっていく必要があります。</p> <p>新規プロダクトの開発は楽しいし、やりがいもある一方で、 会社や事業部の誰かがメインの事業にコミットして数字を成り立たせてくれるからこそ、 チャレンジができます。</p> <p>両立はすごく大変でしたが、改めて数字を成り立たせる重要性を身を持って体感しました。</p> <h3 id="不安との葛藤">不安との葛藤</h3> <p>1人で開発していたということもあり、常に不安との葛藤がありました。 特に、技術が成熟しているわけでもなく、「このコード設計で問題なく動くのかな…」といった不安は常につきまとっていました。</p> <p>加えて、リソースもギリギリな状態で開発を行なっていたので、 納期へのプレッシャーもたくさんありました。</p> <p>不安を解消するために行なったことは、 開発後の「セ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A5%D5%A5%EC">ルフレ</a>ビュー」と「テスト」です。</p> <p>自分が書いたコードが動くわけがないという視点で セ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A5%D5%A5%EC">ルフレ</a>ビューとテストを行うことによって、サービスのクオリティを維持することができました。</p> <p>学んだこととしては、<strong>開発をひと段落した後に安堵、過信をしないこと。</strong> 繰り返し確認・テストすることで、サービスクオリティ維持、不安の解消に繋がることを身を持って知ることができました。</p> <h2 id="まとめ">■まとめ</h2> <p>新卒入社でいきなり新規サービスの開発を任される経験は滅多にないと思うので、 かなり良い経験でした!</p> <p>実際に開発が終わり、リリースした時の感動は凄まじかった…!</p> <p>まだサービスの改善をするところはたくさんあるので、 より良いサービスを目指して、日々開発に勤しもうと思います!</p> <p>最後まで読んでいただきありがとうございました!</p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> r_kamino puppeteerでbootstrap導入時の影響調査をやってみた! hatenablog://entry/4207112889898619828 2022-08-02T13:00:00+09:00 2022-08-02T20:09:28+09:00 こんにちは! 6月から23卒内定者インターン生として株式会社Hajimariに入社した、江端 凌です。 普段は、TUKURÜS事業部で受託開発の業務に携わっています。 今回は受託開発している既存サイトに、bootstrapを導入することになったので、どのような影響が出るのかを調査することになりました。 背景としては、既存サイトのCSSの複雑化と適切なブレークポイントの設置が出来ていなかったことから導入に至りました。 そこでbootstrapの導入前と導入後を比較するために、node.js製のライブラリであるpuppeteerを利用して全ページのスクリーンショットと差分画像を生成しました! 今回… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220722/20220722134957.png" alt="puppeteer&#x3092;&#x4F7F;&#x3063;&#x3066;&#x307F;&#x305F;&#xFF01;" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!<br/> 6月から23卒内定者<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>生として<a href="https://www.hajimari.inc/">株式会社Hajimari</a>に入社した、<a href="https://twitter.com/ebaryo_">江端 凌</a>です。</p> <p>普段は、TUKURÜS事業部で受託開発の業務に携わっています。</p> <p>今回は受託開発している既存サイトに、bootstrapを導入することになったので、どのような影響が出るのかを調査することになりました。</p> <p>背景としては、既存サイトの<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>の複雑化と適切な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D6%A5%EC%A1%BC%A5%AF%A5%DD%A5%A4%A5%F3%A5%C8">ブレークポイント</a>の設置が出来ていなかったことから導入に至りました。</p> <p>そこでbootstrapの導入前と導入後を比較するために、node.js製のライブラリである<strong>puppeteer</strong>を利用して全ページの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>と差分画像を生成しました!</p> <p>今回はpuppeteerの概要から、実装までの流れを紹介させていただきたいと思います!</p> <h2 id="puppeteerとは">puppeteerとは?</h2> <p>puppeteerとは、自動でブラウザ操作を行えるnode.jsのライブラリです。</p> <p>npmでインストールすると、操作する<strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/chromium">chromium</a></strong>も一緒についてきます。<br/>puppeteerは、この<a class="keyword" href="http://d.hatena.ne.jp/keyword/chromium">chromium</a>でブラウザを操作していきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fe-words.jp%2Fw%2FChromium.html" title="Chromiumとは - IT用語辞典" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://e-words.jp/w/Chromium.html">e-words.jp</a></cite></p> <p>puppeteerの概要としては、</p> <ol> <li>ヘッドレスで操作できる。</li> <li>動的に生成されるページ(SPAなど)でも操作ができる。</li> </ol> <p>といった特徴があります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fymzkjpx%2Fitems%2F3bb0c94583c6ff1e3586" title="Puppeteerとは何者なのか少し触ってみた - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/ymzkjpx/items/3bb0c94583c6ff1e3586">qiita.com</a></cite></p> <h2 id="puppeteerを使ってやったこと">puppeteerを使ってやったこと</h2> <p>では、今回puppeteerを使ってやりたいことを確認していきます。</p> <p>bootstrapを既存のサイトに導入した場合、すでに存在している<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>と競合してデザイン崩れを起こす可能性があります。</p> <p>その時、どのような影響が出るのかを調査したいため、</p> <ul> <li>テスト環境(bootstrap未実装)</li> <li>local環境(bootstrap導入済み)</li> </ul> <p>の二つを利用して、puppeteerを使って両方の環境で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>を撮りました。</p> <p>その後、looks-sameというライブラリで差分画像を生成をします。</p> <p>looks-sameもnode.jsのライブラリですので、puppeteerと一緒にインストールできますが、長くなるので今回は割愛させていただきます。</p> <p>例として今回は、<strong>弊社のホームページの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>を自動で撮影するプログラム</strong>を作りましょう!</p> <h2 id="puppeteerを導入してみる">puppeteerを導入してみる</h2> <p>では早速、npmを使ってpuppeteerをインストールしていきます!</p> <p>node.jsをインストールをしていない場合は、<a href="https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09">node.jsのインストール</a>を行なってください。</p> <p>node.jsがインストールされているかどうか<a href="#f-ac59c645" name="fn-ac59c645" title="puppeteer@3.0.0以降は、Node v10.18.1以降の環境が必要になるので、適宜アップデートしてください。">*1</a>は、<code>node -v</code>コマンドで確認できます。 <figure class="figure-image figure-image-fotolife" title="&#x60;node -v&#x60;コマンドの実行"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220716/20220716211805.png" alt="&amp;#x60;node -v&amp;#x60;&#x30B3;&#x30DE;&#x30F3;&#x30C9;&#x306E;&#x5B9F;&#x884C;&#x753B;&#x9762;" width="1200" height="832" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fmaitake9116%2Fitems%2F7825d90c09f3e2f87dea" title="npm入門 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/maitake9116/items/7825d90c09f3e2f87dea">qiita.com</a></cite></p> <h3 id="npmでインストールする">npmでインストールする</h3> <p>まず、puppeteerを使うフォルダを作ります。</p> <p>適当にデスクトップなど(どこでもいいです!)に移動して、以下のコマンドを実行してください。</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ mkdir puppeteer &amp;&amp; cd puppeteer</pre> <p>そうしたらフォルダが作成されていると思います。</p> <p>続いて、npmでpuppeteerをインストールしていきます!</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ npm init &amp;&amp; npm install puppeteer</pre> <p>そうするとREPLで色々聞かれるので、全部Enterキーで答えましょう。</p> <p>使うバージョンなどを聞かれています。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220716/20220716223205.png" alt="npm init &amp;amp;&amp;amp; npm install puppeteer&#x306E;&#x5B9F;&#x884C;" width="639" height="761" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>画像のような表示になったらインストールは完了です。</p> <p>puppeteer単体をインストールしたい方は、</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ npm install puppeteer-core</pre> <p>で単体でインストールすることが可能です。<a href="#f-3f1ea430" name="fn-3f1ea430" title="puppeteer単体でインストールする場合は、chromiumはインストールされないので、注意してください。">*2</a></p> <h3 id="実際にコードを書いてみる">実際にコードを書いてみる</h3> <p>今回は<a href="https://www.hajimari.inc/">弊社のホームページ</a>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>してみます。</p> <p>先ほど作成したpuppeteerフォルダ配下に<code>index.js</code>と画像を保存する<code>dist</code>フォルダを作成しましょう。</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ touch index.js &amp;&amp; mkdir dist</pre> <p>この<code>index.js</code>の中に処理を書いていきます。</p> <p><code>index.js</code>ファイルがエントリーポイントになります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fe-words.jp%2Fw%2F%25E3%2582%25A8%25E3%2583%25B3%25E3%2583%2588%25E3%2583%25AA%25E3%2583%259D%25E3%2582%25A4%25E3%2583%25B3%25E3%2583%2588.html" title="エントリポイントとは - IT用語辞典" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://e-words.jp/w/%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88.html">e-words.jp</a></cite></p> <p>本当は全ページ撮影するなら、ConstsファイルにURLやログイン情報などを保存しておく方が便利ですが、今回は簡易的に<code>index.js</code>だけで書いていきます。</p> <h4 id="indexjs">index.js</h4> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">const</span> puppeteer = require(<span class="synConstant">'puppeteer'</span>); <span class="synComment">/**</span> <span class="synComment"> * 引数に渡した数 * ミリ秒待機</span> <span class="synComment"> * @param {int} msec</span> <span class="synComment"> * @returns setTimeout</span> <span class="synComment"> */</span> <span class="synIdentifier">function</span> sleep(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise(<span class="synIdentifier">function</span> (resolve) <span class="synIdentifier">{</span> setTimeout(<span class="synIdentifier">function</span> () <span class="synIdentifier">{</span> resolve(); <span class="synIdentifier">}</span>, msec); <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> <span class="synComment">// puppeteerの設定</span> (async () =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> browser = await puppeteer.launch(<span class="synIdentifier">{</span> <span class="synComment">// ヘッドレスかどうか</span> headless: <span class="synConstant">false</span>, <span class="synComment">// 10000ミリ秒応答が無かったら終了する</span> timeout: 10000, <span class="synIdentifier">}</span>); <span class="synStatement">const</span> page = await browser.newPage(); await page.setViewport(<span class="synIdentifier">{</span> width: 1920, height: 1080 <span class="synIdentifier">}</span>); <span class="synComment">// ページに移動</span> await page.<span class="synStatement">goto</span>(<span class="synConstant">'https://www.hajimari.inc/'</span>); <span class="synComment">// 画面遷移のアニメーションが終わるまで待機</span> await sleep(8000); <span class="synComment">// スクリーンショットを取ってdistフォルダに保存する。</span> await page.screenshot(<span class="synIdentifier">{</span> path: <span class="synConstant">'dist/hajimari.png'</span>, fullPage: <span class="synConstant">false</span> <span class="synIdentifier">}</span>); <span class="synComment">// ブラウザを閉じる</span> await browser.close(); <span class="synIdentifier">}</span>)(); </pre> <p>上記のコードを書いて保存したら、<code>node index.js</code>をターミナルで実行しましょう。</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ node index.js</pre> <p><code>dist</code>フォルダ配下に<code>hajimari.png</code>が保存されているはずです。</p> <p>こんな感じで ↓</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/ryo_eba/20220716/20220716233343.png" alt="Hajimari&#x306E;&#x30C8;&#x30C3;&#x30D7;&#x30DA;&#x30FC;&#x30B8;" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <br/></p> <h3 id="それぞれのコードについて">それぞれのコードについて</h3> <p>それぞれのコードを見ていきましょう。</p> <p>puppeteerは基本的に非同期で動作するので、<code>async</code>と<code>await</code>で処理を書いていきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fsoarflat%2Fitems%2F1a9613e023200bbebcb3" title="async/await 入門(JavaScript) - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://qiita.com/soarflat/items/1a9613e023200bbebcb3">qiita.com</a></cite></p> <h4 id="puppeteerの設定">puppeteerの設定</h4> <pre class="code lang-javascript" data-lang="javascript" data-unlink>(async () =&gt; <span class="synIdentifier">{</span> <span class="synStatement">const</span> browser = await puppeteer.launch(<span class="synIdentifier">{</span> headless: <span class="synConstant">false</span>, timeout: 10000, <span class="synIdentifier">}</span>); <span class="synStatement">const</span> page = await browser.newPage(); await page.setViewport(<span class="synIdentifier">{</span> width: 1920, height: 1080 <span class="synIdentifier">}</span>); </pre> <p>ここでは、puppeteerの設定ができます。</p> <p>3行目の<code>headless: false</code>は、ヘッドレスにするかどうかを決めています。</p> <p>今回はわかりやすく、<code>false</code>にすることで<a class="keyword" href="http://d.hatena.ne.jp/keyword/chromium">chromium</a>の動きを見えるようにしました。必要なければ<code>true</code>にすることで見えなくなります。</p> <p>8行目で表示するサイズを変えられます。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>await page.setViewport(<span class="synIdentifier">{</span> width: 1920, height: 1080 <span class="synIdentifier">}</span>); </pre> <p>今回はPC表示にしていますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>サイズで撮影したい場合は、<br/> <code>width</code>と<code>height</code>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>のサイズにすることで撮影可能です。</p> <h4 id="スクリーンショットの処理"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>の処理</h4> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// ページに移動</span> await page.<span class="synStatement">goto</span>(<span class="synConstant">'https://www.hajimari.inc/'</span>); <span class="synComment">// 画面遷移のアニメーションが終わるまで待機</span> await sleep(8000); <span class="synComment">// スクリーンショットを取ってdistフォルダに保存する。</span> await page.screenshot(<span class="synIdentifier">{</span> path: <span class="synConstant">'dist/hajimari.png'</span>, fullPage: <span class="synConstant">false</span> <span class="synIdentifier">}</span>); <span class="synComment">// ブラウザを閉じる</span> await browser.close(); </pre> <p>1行目で目的のページに移動します。</p> <p><code>page.goto()</code>に目的のURLを渡してあげましょう。ここを変更すると別のページにも飛ぶことができます。</p> <p>2行目の<code>sleep()</code>は上の方で作った関数です。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synIdentifier">function</span> sleep(msec) <span class="synIdentifier">{</span> <span class="synStatement">return</span> <span class="synStatement">new</span> Promise(<span class="synIdentifier">function</span> (resolve) <span class="synIdentifier">{</span> setTimeout(<span class="synIdentifier">function</span> () <span class="synIdentifier">{</span> resolve(); <span class="synIdentifier">}</span>, msec); <span class="synIdentifier">}</span>); <span class="synIdentifier">}</span> </pre> <p>細かい説明は省きますが、引数に渡した数字 × ミリ秒だけ待機します。</p> <p>なので、<code>await sleep(8000);</code>は8秒待機させています。</p> <p>3行目の処理は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>をして、引数に渡した保存先に保存しています。</p> <p>4行目でブラウザを閉じて、puppeteerの処理を終了しています。</p> <h2 id="まとめ">まとめ</h2> <p>自分はこのpuppeteerアプリを作成したことで、何度も確認したり<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>画面に切り替えたりできて効率化につながりました!</p> <p>またヘッドレスでも動くので、puppeteerを動かしながら別の業務もできます。</p> <p>今回は紹介しきれませんでしたが、差分画像を生成する<code>looks-same</code>というライブラリを使ったり、ベーシック認証を突破したり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Xpath">Xpath</a>で要素を取得してボタンを押したりもできたりして、とても幅広いです。</p> <p>今後もE2Eテスト用に使ったりできそうなので、より良いものにできるように保守していきたいと思っています!</p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-ac59c645" name="f-ac59c645" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">puppeteer@3.0.0以降は、Node v10.18.1以降の環境が必要になるので、適宜アップデートしてください。</span></p> <p class="footnote"><a href="#fn-3f1ea430" name="f-3f1ea430" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">puppeteer単体でインストールする場合は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/chromium">chromium</a>はインストールされないので、注意してください。</span></p> </div> ryo_eba PHPStan導入のすすめ hatenablog://entry/13574176438102396589 2022-06-16T12:00:00+09:00 2022-06-16T12:00:05+09:00 こんにちは! 株式会社Hajimari21卒エンジニアの古田 鏡です。 普段は、TUKURUS事業部(旧PIECE事業部)で 自社プロダクトであるスタートアップ向けマッチングサイト構築パッケージPIECE (https://crowd.itpropartners.com/piece/)の開発や受託開発を行っています! ※2022年4月から事業部の方針変更により事業名が変更となりました! crowd.itpropartners.com 現在ジョインさせていただいている案件では、 組織のオンボーディングシステムの新規機能開発・システム統合等を担当させていただいております。 開発言語に関してはバック… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20220616/20220616113417.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> こんにちは!<br> 株式会社Hajimari21卒エンジニアの<a href="https://twitter.com/furukyo___">古田 鏡</a>です。</p> <p>普段は、TUKURUS事業部(旧PIECE事業部)で<br> 自社プロダクトであるスタートアップ向けマッチングサイト構築パッケージPIECE<br> (<a href="https://crowd.itpropartners.com/piece/">https://crowd.itpropartners.com/piece/</a>)の開発や受託開発を行っています!<br> ※2022年4月から事業部の方針変更により事業名が変更となりました!<br></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcrowd.itpropartners.com%2Fpiece%2F" title="PIECE" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://crowd.itpropartners.com/piece/">crowd.itpropartners.com</a></cite></p> <p>現在ジョインさせていただいている案件では、<br> 組織のオンボーディングシステムの新規機能開発・システム統合等を担当させていただいております。</p> <p>開発言語に関してはバックエンドで<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>はLaravel)を使っていて、<br> 静的解析ツールPHPStanを導入しているのですが、<br> このツールが便利だったので今回はご紹介していきたいと思います!</p> <h3>●<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の特徴</h3> <p>そもそも<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>は「<strong>動的型付け言語</strong>」に分類され、<br> 基本的にはデータ型の宣言がいらない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>です。</p> <p>※参考 <a href="https://qiita.com/wann/items/16920b070dc0483f152a">動的言語と静的言語の違い</a></p> <p>型の宣言がいらないことで得られるメリットとしては、<br></p> <ul> <li>どのような型の値でも代入でき、型(文字列・数値・配列・オブジェクト等)を意識する必要がない</li> <li>(型の宣言がいらない分)記述量が少なくすむ</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>の変更(修正)に強い</li> </ul> <p>この辺が挙げられるのではないかと思います。</p> <p>私もこれまで実際<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>の案件をやっている中で、<br> 型を意識せずに面倒から解放される点で<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>最高ー!!と思っていました。</p> <p>ですが、現在携わっている案件で、複数人での開発・システム統合をする中で、<br> コードの統合・不要コード削除に伴い、影響範囲の大きな修正が増えたことで、<br> 静的解析の重要性を身に染みて感じました、、、<br> (修正がバグを生む可能性大、、、テストコードを全て書くのは時間がかかりますし、、、)</p> <p><u>そこでPHPStanの出番です!!</u></p> <h3>●PHPStanとは?</h3> <p>PHPStan は、<br> <a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a> コードの静的解析ツールです。 (<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a> Static Analysis Toolの略)<br> <a href="https://phpstan.org/">https://phpstan.org/</a></p> <p>MIT ライセンスで公開されており、<br> Composer でインストールして利用できます。<br> また、Docker イメージも公式で公開されております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fphpstan%2Fphpstan" title="GitHub - phpstan/phpstan: PHP Static Analysis Tool - discover bugs in your code without running it!" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/phpstan/phpstan">github.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fphpstan.org%2Fuser-guide%2Fdocker" title="Docker" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://phpstan.org/user-guide/docker">phpstan.org</a></cite></p> <h5>概要</h5> <ul> <li>動作には<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a> 7.2以降が必要</li> <li>未定義の変数・メソッド・Class・プロパティなどを検出</li> <li>PHPDocの構文チェックが可能</li> </ul> <h3>●ルールレベル設定</h3> <p>PHPStanはルールレベル設定があり、<br> レベルは<u><strong>0~9の10段階</strong></u>で設定できます!<br> レベル9ともなると結構厳しめのチェックとなるようです、、、!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fphpstan.org%2Fuser-guide%2Frule-levels" title="Rule Levels" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://phpstan.org/user-guide/rule-levels">phpstan.org</a></cite></p> <table> <thead> <tr> <th> Lev </th> <th> 内容 </th> </tr> </thead> <tbody> <tr> <td> 0 </td> <td> 基本的なチェック、未知のクラス、未知の関数、$this上で呼び出された未知のメソッド、それらのメソッドや関数に渡された引数の数が間違っている、常に未定義の変数をチェック </td> </tr> <tr> <td> 1 </td> <td> 未定義の変数、<strong>call と </strong>get を持つクラスの未知のマジックメソッドとプロパティがある可能性がある </td> </tr> <tr> <td> 2 </td> <td> ($this だけでなく)すべての式で未知のメソッドをチェックし、PHPDocs を検証する </td> </tr> <tr> <td> 3 </td> <td> 戻り値の型、プロパティに割り当てられた型の確認 </td> </tr> <tr> <td> 4 </td> <td> 基本的なデッドコードチェック - instanceofやその他の型チェックが常にfalse、到達しないelse文、return後の到達<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C9%D4%C7%BD">不能</a>コードなど </td> </tr> <tr> <td> 5 </td> <td> メソッドや関数に渡される引数の型チェック </td> </tr> <tr> <td> 6 </td> <td> タイプヒントの欠落を報告する </td> </tr> <tr> <td> 7 </td> <td> 部分的に間違っている<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%C0%CD%FD%CF%C2">論理和</a>型の報告 - <a class="keyword" href="http://d.hatena.ne.jp/keyword/%CF%C0%CD%FD%CF%C2">論理和</a>型の一部の型にしか存在しないメソッドを呼び出した場合、レベル7はそのことを報告し始めます(その他の不正確な状況も) </td> </tr> <tr> <td> 8 </td> <td> null可能な型に対するメソッド呼び出しとプロパティへのアクセスを報告する </td> </tr> <tr> <td> 9 </td> <td> 混合型に厳密であること - この型で唯一許される操作は、この型を別の混合型に渡すことである </td> </tr> </tbody> </table> <p>下記に例題が載っているのでチェックしてみてください!<br></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fphpstan.org%2Ftry" title="Playground" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://phpstan.org/try">phpstan.org</a></cite></p> <h3>●インストール方法</h3> <p>インストールする方法としては<br></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fphpstan.org%2Fuser-guide%2Fgetting-started" title="Getting Started" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://phpstan.org/user-guide/getting-started">phpstan.org</a></cite></p> <p>公式にインストールする方法が記載してあるので、それに従いインストールします。<br></p> <p>Composerを使い下記のコマンドを実行します。<br></p> <pre class="code" data-lang="" data-unlink>composer require --dev phpstan/phpstan</pre> <p>基本的に開発環境で使用するものだと思いますので、<br> --devオプションをつけるのが良いと思います。<br><br></p> <p>インストールできているか確認<br></p> <pre class="code" data-lang="" data-unlink>./vendor/bin/phpstan analyse --version PHPStan - PHP Static Analysis Tool 0.12.99⇦インストールされているバージョンを表示</pre> <h3>●実行方法</h3> <h5>実行コマンド</h5> <pre class="code" data-lang="" data-unlink>./vendor/bin/phpstan analyse</pre> <p>ルールレベルを変更する場合には、 レベルを変更するには --level (-l )オプションで実行します。<br> ↓ではレベル4で設定しています。</p> <pre class="code" data-lang="" data-unlink>.\vendor\bin\phpstan analyse -l 4</pre> <h5>設定ファイル(phpstan.<a class="keyword" href="http://d.hatena.ne.jp/keyword/neon">neon</a> 一部抜粋)</h5> <pre class="code" data-lang="" data-unlink>includes: - ./vendor/nunomaduro/larastan/extension.neon parameters: paths: - app level: 4</pre> <h5>実行結果</h5> <p>エラーありの場合<br> <figure class="figure-image figure-image-fotolife" title="error"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20220616/20220616085354.png" width="1200" height="511" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure> エラーなしの場合<br> <figure class="figure-image figure-image-fotolife" title="no_error"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20220616/20220616085438.png" width="1200" height="203" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></figure></p> <p>こんな感じで結果が出ます。</p> <h3>●実際に使ってみて得られたこと</h3> <ul> <li><p><strong>導入のしやすさ</strong><br> 基本的に公式通りでインストールでき、導入のハードルが低いこと。<br></p></li> <li><p><strong>解析が高速にできる</strong><br> 状況に応じて、ルールレベル・実行範囲を変えられ、必要部分に対して解析が高速にできること<br></p></li> <li><p><strong>レビューの負担軽減</strong><br> システム統合など大きな改修を行う場合、コードレビューの量が多くなりがちですが、<br> 事前に静的解析を行うことでレビューの量を減らせること<br> (個人的にはこの効果が一番大きいと思いました。)<br></p></li> <li><p><strong>ある程度システムの安全性・整合性を担保できる</strong><br> 型レベルの安全性・整合性が保たれること<br> しかしながら、無限ループや関数の型がmixed・null許容の場合、解析をすり抜けてしまうことがあること<br></p></li> </ul> <h3>●まとめ</h3> <p>大前提として、別途<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8">ユニットテスト</a>等は必要だと思いますが<br> <u>導入と実行の容易さ、時間短縮の観点から非常に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%B9%A5%D1">コスパ</a>の良いツールだと思いました!</u><br> プロジェクトが大きくなればなるほど、効果を発揮するツールだと思いますので、是非導入してみてはいかがでしょうか!<br></p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> kyoK Notionで新卒採用管理システムを作ってみた! hatenablog://entry/13574176438099216720 2022-06-06T12:00:00+09:00 2022-06-06T23:28:48+09:00 23卒採用が本格的に始まる前にNotionで新卒採用管理システムを作成しました。 実際に1年間運用してみてよかったことや、改善できた部分に関してお伝えできればと思います! <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20220605/20220605171936.png" width="1200" height="630" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> こんにちは!<br /> 4月に入社した、株式会社Hajimari22卒エンジニアの<a href="https://twitter.com/ryotaro00963">神野 凌太郎</a>です。</p> <p>普段は、<a href="https://itpropartners.com/hr/">人事プロパートナーズ</a>の開発業務や事業部内でリリースする新規サービスの開発業務を担当しています。</p> <p>本業はエンジニアですが、内定者時代から23卒エンジニア採用の兼務をしていて、<br /> 約1年前、23卒採用が本格的に始まる前にNotionで新卒採用管理システムを作成しました。</p> <p>今回は、実際に1年間運用してみてよかったことや、改善できたなと思うことを まとめてお伝えできればと思います!</p> <h2>■そもそもNotionとは</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.notion.so%2Fja-jp" title="Notion (ノーション) – すべてのチームをひとつのワークスペースで" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.notion.so/ja-jp">www.notion.so</a></cite></p> <p>Notionは、ドキュメントやデータベース、TODO管理など<br /> あらゆる情報やデータを1つに集約して管理できる<a class="keyword" href="http://d.hatena.ne.jp/keyword/SaaS">SaaS</a>型のツールです。<br /></p> <p>弊社では、Notionを社内<a class="keyword" href="http://d.hatena.ne.jp/keyword/wiki">wiki</a>として全社で導入しています。<br /> 他にも各事業部で、チームのタスク管理やTODO管理で活用していたり、<br /> 採用募集のページを公開したりしています。</p> <p>また約半年前に、弊社のイベントスペースで<br /> NotionTokyoのオフラインイベントが行われたりもしました!<br /></p> <p><a href="https://notiontokyo14.splashthat.com">https://notiontokyo14.splashthat.com</a></p> <h2>■Notionを導入した経緯</h2> <p>Notionシステムを導入する前は、 Slackでチャンネルを作成し、そのチャンネルで候補者ごとにスレッドを立てて申し送りや引き継ぎが行われていました。</p> <h3>課題</h3> <p>Slackを使って申し送りを行うことで、スピーディーに情報共有ができる一方で、 下記の問題点がありました。</p> <ul> <li>候補者の情報を見るために、過去のスレッドを辿る必要がある(ピーク時にはすぐに上に流れていってしまうので、スレッドを見つけるのは困難です)</li> <li>ステータス管理を別でスプシで行っていたが、 更新漏れがたびたび発生しており、リアルタイムでのステータス管理ができていなかった</li> <li>情報が点在しているため、データ分析をすることが難しく定性的な判断で採用活動を行なっていた</li> </ul> <p>実装する手間を最小限にリアルタイムにデータを集約しようということで、 Notionで新卒採用管理システムを作成することにしました。</p> <h2>■全体の流れ</h2> <p>Notionを使った一連のフローを下記の図にまとめました。<br /> Notionを使うのは、23卒採用メンバー + 面接を担当する社員です。</p> <p>初回<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%DC%BF%A8">接触</a>の場合は、学生テーブルにレコードを追加し、<br /> 基本情報(名前や次回面接日)や関連情報(大学、応募経由など)を記入します。</p> <p>面接後は評価や所感を記入し、社内の専用Slackに情報共有、エージェントや学生に結果連絡を行います。</p> <p>2回目以降の面接に関しては、基本的に23卒採用メンバー以外の社員が担当します。<br /> 担当社員は、面接終了後にNotionのステータスの更新、結果・所感の記入を行い、<br /> リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>(該当学生を担当している23卒採用メンバー)にSlackで連絡を行います。 <figure class="figure-image figure-image-fotolife" title="Notionを活用したときの一連のフロー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20220605/20220605164604.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Notionを活用したときの一連のフロー</figcaption></figure></p> <h2>■テーブル構成</h2> <p>テーブル構成は以下の通りです。 学生テーブルを中心として、それぞれ「採用メンバー」「大学」「エージェント」「リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>」のテーブルと連携しています。「リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>」は23卒採用メンバーを指し、 「採用メンバー」は2次面接以降を担当する社員を指します。</p> <p>Notionのリレーション機能を使った実装を行い、リレーションを利用したのはそれぞれのテーブル別でデータを蓄積するためです。 <figure class="figure-image figure-image-fotolife" title="テーブル構成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20220605/20220605164746.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>テーブル構成</figcaption></figure></p> <h2>■作成にあたって意識したこと</h2> <p>大前提として、23卒採用メンバーにNotionを活用してもらわなければ意味がありません。 導入目的は、リアルタイムにステータス、情報更新してもらうことだからです。</p> <p>ゴールは、メンバーに<strong>「使ってもらえるかどうか」。</strong> そのため、多少データベースの構成が複雑になっても、UIを優先しました。</p> <h3>トップページの親しみ</h3> <p>メンバーはかなりの頻度でNotionを開くので、トップページをこだわろうと思いました。 特にカバー画像は目に優しく、可愛くしようと思っていた時に、たまたま近くに同期の女の子がいたので、作成を依頼しました。めちゃくちゃ可愛い! <figure class="figure-image figure-image-fotolife" title="トップページ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20220605/20220605164850.png" width="1200" height="651" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>トップページ</figcaption></figure></p> <h3>使い方ページの作成</h3> <p>当時、全社でNotionが導入されたばかりだったこともあり、 Notionの操作に慣れないメンバーがほとんどでした。</p> <p>また、Notionを使うのは23卒採用メンバーだけではなかったので、<br /> 多くの人に等しく操作に慣れてもらう必要がありました。<br /> そこで、使い方ページの作成を行いました。</p> <ul> <li>文字だけではなく動画を使って説明すること</li> <li>リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>と面接担当者別のフローに沿って、必要な情報だけ提示すること</li> </ul> <p>意識したことは上記の2つです。<br /> 使い方ページを導入したおかげで、<br /> 導入障壁があまりない状態で、運用をスタートさせることができました。</p> <h3>記入コストを下げること</h3> <p>冒頭でも記述した通り、ゴールは使ってもらえるかどうかです。<br /> そのため、記入するコストを下げることを意識しました。<br /> 具体的には、<strong>考えなくても直感的に記述できること。</strong></p> <p>まず、Notionのテンプレート機能を利用し、<br /> 極力労力をかけないで良いように、学生ページのテンプレートを用意しました。 <figure class="figure-image figure-image-fotolife" title="学生ページのテンプレート利用方法"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20220605/20220605164939.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>学生ページのテンプレート利用方法</figcaption></figure> 基本情報は、一覧画面で情報の記入や更新ができるようにし、<br /> 詳細な情報(学生情報、面談メモ)は学生ページで記述できるようにしました。<br /></p> <p>見やすさも意識しつつ、記入に思考体力を奪わないように設計を心がけました。</p> <h2>■問題点</h2> <p>実際に1年間運用してみた結果、いくつか問題点が出てきました。</p> <h3>ページが重く、表示速度が遅い</h3> <p>最もクリティカルだったのは、ページが重く、表示速度が遅いことです。<br /> 原因は、<strong>学生レコードの急激な増加</strong>と<strong>リレーションを貼りすぎたこと</strong>だと推測できます。</p> <p>年度の途中で採用予定人数が大幅に増えたことも相まって<br /> 学生レコードが増加し、現在700レコード近くデータが入っています。</p> <p>その上、そのレコードに各関連テーブルのリレーションが貼られている状態なので、<br /> さらにページを重くしています。</p> <p>学生ページ内、面談評価の面談担当者と採用メンバーテーブルがリレーションを貼っており、特に最も重くしている原因として考えられます。<br /> 学生テーブルの学生ページ内でリレーションを貼っているので、採用メンバー全員が面談担当者と紐付いた状態になっているからです。<br /> (しかも、採用メンバーテーブルであまりデータを貯める必要がないことも後からわかったので、リレーションを貼る必要性もありません。) <figure class="figure-image figure-image-fotolife" title="学生ページ内の面談評価テーブル"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20220605/20220605165035.png" width="1200" height="382" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>学生ページ内の面談評価テーブル</figcaption></figure> この後に、シンプルテーブルの機能がリリースされました。<br /> 採用メンバーのテーブルはこれがベストだと感じました。(zoomのリンクや<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wantedly">Wantedly</a>の記事の管理は行いたいからです)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.notion.so%2Fja-jp%2Freleases%2F2021-11-16" title="2021年11月16日 – Notion 2.14:シンプルテーブル 🏓" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.notion.so/ja-jp/releases/2021-11-16">www.notion.so</a></cite></p> <h3>データ集計が難しい</h3> <p>残念ながら、Notionには<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>やエクセルのように便利な関数がいくつも用意されていません。<br /> 当初はNotion内でデータ分析がリアルタイムに見れる状態を作ろうと思い、<br /> 用意されている関数を駆使して、無理やり集計ができるようにしましたが、<br /> あまり活用できていません。</p> <p>データ分析自体は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>をNotionに埋め込んで同期できるように運用した方が良さそうです。</p> <h2>■まとめ</h2> <p>採用管理システムをNotionで作成したことによって、 データを一元に管理できるようになったので非常に良かったです!<br /> 実際に運用してみて問題点が浮き彫りになったので、 24卒以降の管理システムは、より良いシステム構築ができれば良いなと思っています。<br /> 1年前に作成した時よりも、機能がかなり充実しているので、 うまく活用していきたいと思います。</p> <p>Notion大好き!</p> <hr /> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、<br /> 一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br /> みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F910364" title="Vue.js×Nest.jsで新規プロダクトを開発したいエンジニア募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/910364">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F802044" title="新拠点・長野オフィスで成長したいWebエンジニア・新卒・インターン生募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/802044">www.wantedly.com</a></cite> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> r_kamino 22卒内定者が『フリーランス人事と案件のマッチングスコア算出』機能をリリースしました! hatenablog://entry/13574176438035386926 2021-11-22T13:18:38+09:00 2021-11-22T13:18:38+09:00 皆さん初めまして、22卒内定者で人事プロパートナーズで開発を担当している、神野凌太郎と申します! 今回、人事プロパートナーズ内で「フリーランス人事と案件のマッチングスコア算出」機能をリリースしたので、リリースに至った経緯と機能詳細についてご紹介したいと思います! ■人事プロサービス概要 各社が抱える組織課題の解決に強みを持った即戦力人事の業務委託人材と人事のリソース不足でお困りの成長企業をマッチングするサービスです。 興味のある方は、ホームページからぜひお問い合わせください! itpropartners.com ■機能開発に至った背景 弊サービスでは、案件をご紹介する前に専属CA(キャリアーエ… <p>皆さん初めまして、<br />22卒内定者で人事プロパートナーズで開発を担当している、<a href="https://twitter.com/ryotaro00963">神野凌太郎</a>と申します!</p> <p>今回、人事プロパートナーズ内で<strong>「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事と案件のマッチングスコア算出」</strong>機能をリリースしたので、リリースに至った経緯と機能詳細についてご紹介したいと思います!</p> <h3>■人事プロサービス概要</h3> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20211122/20211122124847.png" alt="f:id:r_kamino:20211122124847p:plain" width="650" height="274" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>各社が抱える組織課題の解決に強みを持った即戦力人事の業務委託人材と<br />人事のリソース不足でお困りの成長企業をマッチングするサービスです。</p> <p>興味のある方は、<br />ホームページからぜひお問い合わせください!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fitpropartners.com%2Fhr%2F" title="人事プロパートナーズ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://itpropartners.com/hr/">itpropartners.com</a></cite></p> <h3>■機能開発に至った背景</h3> <p>弊サービスでは、案件をご紹介する前に専属CA(キャリアーエージェント)がご登録いただいた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事と面談を行います。</p> <p>面談では、</p> <ul> <li>どういう経験をされてきたか</li> <li>案件にはどのくらいの日数や、週何時間くらい稼働できるか</li> </ul> <p>などをお聞きして、管理画面に面談情報を登録していきます。</p> <p>その後、登録した情報をもとに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事に案件をご紹介しています。<br />ただ、弊サービスには多くの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事の方にご登録いただいているため、企業担当が案件にマッチする<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事をピックアップするのが困難です。<br />そのため、企業担当からCAに案件概要を説明した後、<br />CAが案件にマッチする<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事ピックアップしています。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20211122/20211122124958.png" alt="f:id:r_kamino:20211122124958p:plain" width="501" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>問題点としては、</p> <ul> <li>企業担当とCAのコミュニケーションコストが大きいこと</li> <li>CAの属人的な情報で、案件と<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人材のマッチングを行なっていること</li> </ul> <p>が挙げられます。</p> <p>そこで、「人と案件のマッチングスコア算出」機能をリリースすることで、<br />少しでも企業担当とCAの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%A9%BF%F4">工数</a>を削減し、より最適なマッチングを実現しようと考えたわけです。</p> <h3>■マッチングスコア算出のロジック</h3> <p>マッチングスコアは、以下の8項目それぞれの入力内容を突き合わせて算出します。<br />具体的には、それぞれの項目で0~1の間で評価を行い、設定した重みをかけ合わせて<br />スコアを算出しています。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20211122/20211122130155.png" alt="f:id:r_kamino:20211122130155p:plain" width="515" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <p>企業担当とCAの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%A2%A5%EA">ヒアリ</a>ング結果から、<br /><strong>「月間稼働時間」「勤務形態」「タグ」「稼働可能時間帯」</strong>がマッチングにおいて特に大事な項目だということがわかったので、他の項目に比べて重みが大きくなっています。</p> <p>また、<strong>案件・<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事のどちらか未入力の項目があった場合は、</strong><br /><strong>自動的にその項目は0として評価</strong>をします。<br />項目によっては未入力のほうが入力していた時よりもスコアが大きくなる場合があるからです。</p> <h3> ■実装手順</h3> <h4> ①仮の評価方法を作成</h4> <p>まず、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事と案件の入力項目を見比べて、マッチングに必要な項目を洗い出しました。<br />項目を洗い出すなかで、どちらか一方にしか項目でも、マッチングに使える項目と判断したものに関しては、新たに項目を追加する想定でピックアップをしました。<br />ここで、既存のデータにこだわるのではなく、あくまでマッチングに必要な要素は何かという判断軸でピックアップを行うことを意識しました。</p> <h4>②過去データをもとに、手動で評価値を算出</h4> <p>過去の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事とその方が稼働している案件情報を用いて、評価値を算出しました。<br />実際にマッチングが成立した情報を使った時に、評価値に問題がないかを確認するためです。</p> <p>結果として、全ての項目を0、1だけで評価してしまうと、明らかに他の項目より評価が高くなってしまう項目があったので、それぞれの項目に重みをつける方法を取ることにしました。<br />データベースに重みの値を保存できるようにして、いつでも値を変えられるようにします。</p> <h4>③CAと企業担当に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%A2%A5%EA">ヒアリ</a>ング</h4> <p>②までに作成した叩きを用いて、CAと企業担当と要件のアップデートを行いました。<br />マッチングに用いる項目と重みに問題がないかを確認してもらい、評価方法の改善をするのが目的です。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%A2%A5%EA">ヒアリ</a>ングにおいてCAと企業担当どちらかに偏った意思決定にならないように、<br />中立に意見を取りまとめるように心がけました。</p> <h4>④入力フォームの設置</h4> <p>要件のアップデートで新たに出てきたマッチング項目を管理画面で登録できるように、入力フォームの設置を行いました。(システム設計上、複数のデータベースと入力ページの改修が必要で、想定以上の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%A9%BF%F4">工数</a>がかかりかなり大変でした。。)</p> <h4>⑤自動マッチングの実装</h4> <p>マッチングに必要な項目を入力できるようにしたので、最後に自動マッチングの実装に取り掛かりました。<br />アップデートした要件をもとに、それぞれの項目でスコア算出のコードを書きました。(全部で200行くらいのボリュームです、、)</p> <p>また、算出したスコアを用いて「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事→案件」「案件→<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事」でマッチング検索できるページを実装しました。</p> <p>下記の図は、「案件→<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事」で検索したページの例です。<br />算出したスコアの高い順でユーザー表示を行うことで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事と案件の最適なマッチングを提案します。</p> <p> </p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/r/r_kamino/20211122/20211122130552.png" alt="f:id:r_kamino:20211122130552p:plain" width="1200" height="520" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p> <h3>■開発を通して大変だったこと</h3> <p>最も大変だったことは、<strong>自動マッチングの抽象的な要件を具体に落とし込む作業</strong>です。</p> <p>もともと、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事と案件を自動マッチングできたら良いよね」と開発チームでの何気ない会話からスタートしているので、「やりたいことは決まっているけど、具体的には何も決まっていない」状態でした。</p> <p>そのため、具体的な実装要件を詰める前に、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>をプログラムに置き換える作業が必要です。</p> <p>まず、スラック上で企業担当とCAがやり取りしているテキストからマッチングに必要な項目を推測し、開発チームで叩きを作成することから始めました。(叩きを作成するのに半日以上かかりました、、)</p> <p>「叩きがある状態だと効率的に要件を詰めることができる」と上長から教えてもらったという背景から、叩きを先に作成することにしました。</p> <p>実際に、叩きがある状態で議論をすると、より本質的なマッチング要件を決めることができ、<br />実装もスムーズに行うことができました。</p> <p>学んだこととしては、<strong>「実装前の要件整理に時間をかけること。」</strong><br />時間をかけた分だけ、ビジネスに寄り添った機能開発ができることを実感しました。</p> <h3>■まとめ</h3> <p>実際にこの機能をリリースすることで、</p> <ul> <li><strong>月間12.5時間の削減</strong></li> <li><strong>客観的な情報で誰でも人選が可能な状態</strong></li> </ul> <p>を実現することができました。</p> <p>実際にCAと企業担当からは、「最適なマッチングが実現できて、リソースが余った分、さらに別のところで価値提供が行えるようになった」というお声をいただきました!</p> <p>今後、この機能を活用して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EA%A1%BC%A5%E9%A5%F3%A5%B9">フリーランス</a>人事の方に直接案件をレコメンドできる機能のリリースも考えています。</p> <p>他にもまだ活かしきれていないデータは数多くあるので、うまくデータ活用を行なってサービス向上に寄与していきたいと思います!</p> <p>最後までお読みいただきありがとうございました!</p> <p>------------------------------------------------------------------------------------</p> <p>株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、一緒に開発を行なっていただけるエンジニア募集しています!</p> <p>23新卒エンジニアの募集も開始しておりますので、少しでも興味ある方はカジュアルにお話ししましょう!</p> <p>興味のある方は以下の記事をぜひご覧ください!<br />みなさまとお会いできるのを心からお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F779070" title="23卒エンジニア職 | 文理不問!本気でエンジニアになりたい人を募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/779070">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F169340" title="【20卒内定者】夢に本気になれる環境。だから僕はITプロパートナーズを選んだ | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/169340">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F208075" title="【20卒内定者】国公立大学を中退してITベンチャーで働き始める理由 | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/208075">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F177689" title="【20卒内定者】give軸をはき違えた就活生が苦労した話。 | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/177689">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F338462" title="理系院生が大手志向を捨てて、Hajimariを選んだ理由 | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/338462">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F341581" title="就活嫌いだった大学生が夢を叶えるためにHajimariへ | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/341581">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fpost_articles%2F343404" title="Hajimariメンバーと共に人生の正解を追い求め続けようと思った理由。 | 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/post_articles/343404">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fusers%2F27256041%2Fpost_articles%2F338620" title="ベンチャーに新卒入社して悩んでいた僕が、1年後にVP賞を受賞できた話" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/users/27256041/post_articles/338620">www.wantedly.com</a></cite></p> r_kamino 一泊二日開発合宿!〜思わず移住したくなる森のオフィス〜 hatenablog://entry/26006613650712989 2020-11-13T16:38:16+09:00 2020-11-13T16:38:16+09:00 こんにちは! 株式会社Hajimari21卒内定者エンジニアの古田です。 今年の6月より内定者インターンとしてHajimariで働いております! 普段は、企業と優秀な人事のマッチングサービス 「人事プロパートナーズ」の開発業務を行っております! 毎日学ぶ事だらけですが、会社の良いメンバーにも恵まれ、 非常に充実した日々を送っております!!! やっぱり、仕事をする上で誰と働くかが非常に重要だなと 最近は、そんなことを実感しております! さて、今回は社内のエンジニアメンバーと共に一泊二日で開発合宿に行ってきました! その様子をこのブログで紹介させていただきます! 開発合宿を行った「富士見 森のオフ… <p>こんにちは!</p> <p>株式会社Hajimari21卒内定者エンジニアの古田です。</p> <p>今年の6月より<span style="text-decoration: underline;">内定者<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a></span>としてHajimariで働いております!</p> <p> </p> <p>普段は、企業と優秀な人事のマッチングサービス</p> <p>「<a href="https://itpropartners.com/hr/">人事プロパートナーズ</a>」の開発業務を行っております!</p> <p> </p> <p>毎日学ぶ事だらけですが、会社の良いメンバーにも恵まれ、</p> <p>非常に充実した日々を送っております!!!</p> <p>やっぱり、<strong>仕事をする上で誰と働くかが非常に重要だな</strong>と</p> <p>最近は、そんなことを実感しております!</p> <p><br /><cite class="hatena-citation"></cite></p> <p>さて、今回は社内のエンジニ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%F3%A5%D0%A1%BC">アメンバー</a>と共に<strong>一泊二日で開発合宿に行ってきました!</strong></p> <p>その様子をこのブログで紹介させていただきます!</p> <p> </p> <p> </p> <h2><strong>開発合宿を行った「富士見 森のオフィス」</strong> </h2> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109144216.png" alt="f:id:kyoK:20201109144216p:plain" title="f:id:kyoK:20201109144216p:plain" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201111/20201111121752.png" alt="f:id:kyoK:20201111121752p:plain" title="f:id:kyoK:20201111121752p:plain" class="hatena-fotolife" itemprop="image" /></p> <p>(森のオフィス ホームページより)<br /><br /></p> <p>今回、開発合宿は長野県にある</p> <p>「<a href="https://www.morino-office.com/">富士見 森のオフィス</a>」という施設に行ってきました!</p> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C2%E7%BC%AB%C1%B3">大自然</a>の中にあるこの施設は、<span style="color: #333333; font-family: futura-lt-w01-light, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;"><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%EF%A1%BC%A5%AD%A5%F3%A5%B0%A5%B9%A5%DA%A1%BC%A5%B9">コワーキングスペース</a></strong>と</span><span style="color: #333333;"><strong>宿泊棟</strong>が併設されており、</span></p> <p>普段は<span style="color: #333333; font-family: futura-lt-w01-light, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;"><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A5%C6%A5%E9%A5%A4%A5%C8%A5%AA%A5%D5%A5%A3%A5%B9">サテライトオフィス</a></strong>や<strong>テレワーク拠点</strong>、</span></p> <p><span style="color: #333333; font-family: futura-lt-w01-light, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline !important;">地域住民の方々にとっての<strong>“公民館”的スペース</strong>として使われている施設で、</span></p> <p>Hajimariのオフィスがある<span style="text-decoration: underline;">渋谷からは車で2時間半程</span>のところにあります!</p> <p> </p> <p> </p> <p> </p> <p> </p> <h2><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%EF%A1%BC%A5%AD%A5%F3%A5%B0%A5%B9%A5%DA%A1%BC%A5%B9">コワーキングスペース</a>の様子</strong></h2> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109145440.jpg" alt="f:id:kyoK:20201109145440j:plain" title="f:id:kyoK:20201109145440j:plain" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109145358.jpg" alt="f:id:kyoK:20201109145358j:plain" title="f:id:kyoK:20201109145358j:plain" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <p>施設内はとても静かで、<strong>ストレスを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C8%F9%BF%D0%A4%E2">微塵も</a>感じさせない</strong></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%EF%A1%BC%A5%AD%A5%F3%A5%B0%A5%B9%A5%DA%A1%BC%A5%B9">コワーキングスペース</a>での作業はめちゃめちゃ集中できました!</p> <p>少し疲れてきた時にパソコンから目を逸らし、</p> <p><strong>窓の外に目を向けると辺りには木々が広がっていて癒されました!</strong></p> <p><span style="text-decoration: underline;">開放感が半端なかったです!!</span></p> <p> </p> <p>施設の外のベンチで作業をしているメンバーもいましたが、</p> <p>周りに木々の音しか聞こえない中での作業は</p> <p><strong>都心で働いていては味わう事ができない</strong>ですし、</p> <p>そんな<span style="text-decoration: underline;">ストレスフリーな環境</span>がこの施設の一番の魅力だと思いました!</p> <p> </p> <p> </p> <h2><strong>施設周辺の様子</strong> </h2> <figure class="figure-image figure-image-fotolife mceNonEditable" title="紅葉"> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109144914.jpg" alt="f:id:kyoK:20201109144914j:plain" title="f:id:kyoK:20201109144914j:plain" class="hatena-fotolife" itemprop="image" /></p> <figcaption class="mceEditable"></figcaption> </figure> <p>森のオフィスというだけあって、辺りは木々に囲まれていました!</p> <p>紅葉シーズンだった事もあり、<span style="text-decoration: underline;">とても綺麗で癒されました!</span></p> <p>(僕の語彙力では表現し切れない事が非常に悔しいです、、、)</p> <p> </p> <p><strong>昼食</strong></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201111/20201111114316.jpg" alt="f:id:kyoK:20201111114316j:plain" title="f:id:kyoK:20201111114316j:plain" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201111/20201111114337.jpg" alt="f:id:kyoK:20201111114337j:plain" title="f:id:kyoK:20201111114337j:plain" class="hatena-fotolife" itemprop="image" /></p> <p>昼食は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%EF%A1%BC%A5%AD%A5%F3%A5%B0%A5%B9%A5%DA%A1%BC%A5%B9">コワーキングスペース</a>の外に<span style="text-decoration: underline;">キッチンカー</span>が来ており、</p> <p>僕はココナッツチキンカレーを頼みました!</p> <p>美味しかったです、そして健康的!!!</p> <p>食べ終わった後、</p> <p>施設内のキッチンで<span style="text-decoration: underline;">自分の使った食器を洗うと50円引き</span>してくれます!!!</p> <p> </p> <p>食器洗いをしている時に、</p> <p>「<strong>〜日より移住して来ました</strong>」という声がちらほら聞こえていたのですが、</p> <p>これだけ居心地の良い場所なら<span style="text-decoration: underline;">移住したくなる気持ちが分かる</span>なと思いました。</p> <h3> </h3> <h2><strong>宿泊施設の様子</strong></h2> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109144846.jpg" alt="f:id:kyoK:20201109144846j:plain" title="f:id:kyoK:20201109144846j:plain" class="hatena-fotolife" itemprop="image" /></p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109163629.png" alt="f:id:kyoK:20201109163629p:plain" title="f:id:kyoK:20201109163629p:plain" class="hatena-fotolife" itemprop="image" /></p> <p>(森のオフィス ホームページより)</p> <p> </p> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%EF%A1%BC%A5%AD%A5%F3%A5%B0%A5%B9%A5%DA%A1%BC%A5%B9">コワーキングスペース</a>の隣にある宿泊施設はとても綺麗でした!!!</p> <p>今回は、偶然にも他の団体のお客さんがいなかったので貸し切り状態でした!</p> <p><span style="text-decoration: underline;">将来はこんな家に住みたい、、、</span>思わずそう思ってしまいました!</p> <p> </p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109163046.jpg" alt="f:id:kyoK:20201109163046j:plain" title="f:id:kyoK:20201109163046j:plain" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <p>夕食は施設の近くにある</p> <p>生産者直売所の「<a href="https://www.tateshinafree.co.jp/haramura/">たてしな自由農村</a>」で</p> <p>猪や鹿の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B8%A5%D3%A5%A8">ジビエ</a>、野菜、地酒などを購入し、</p> <p>宿泊施設のキッチンで調理して食べました!!</p> <p> </p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109161807.jpg" alt="f:id:kyoK:20201109161807j:plain" title="f:id:kyoK:20201109161807j:plain" class="hatena-fotolife" itemprop="image" /></p> <p>特に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B8%A5%D3%A5%A8">ジビエ</a>が最高でした!!!</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109163033.jpg" alt="f:id:kyoK:20201109163033j:plain" title="f:id:kyoK:20201109163033j:plain" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <p>美味しいご飯を食べながら、</p> <p>普段はしないような話をできましたし、</p> <p><strong>よりコミュニケーションが深まりました!!</strong></p> <p>これもまた、<span style="text-decoration: underline;">開発合宿の良さ</span>なのではないかと思います!</p> <h3> </h3> <h3> </h3> <h2><strong>途中から登山合宿に・・・</strong> </h2> <p>初日に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%EF%A1%BC%A5%AD%A5%F3%A5%B0%A5%B9%A5%DA%A1%BC%A5%B9">コワーキングスペース</a>の管理者の方から、</p> <p>今の時期(11月初旬)は宿泊施設の近くにある</p> <p>富士見パノラマリゾートという施設のゴンドラで山を登ると</p> <p><strong><span style="text-decoration: underline;">日の出と共に雲海見えるよ!</span></strong>というお話を聞いたので、</p> <p>メンバー皆で<span style="text-decoration: underline;">早朝4時に起きて</span>車でゴンドラに乗りに行きました!</p> <p> </p> <p><strong>ところが、、、</strong></p> <p> </p> <p>その日に限って、ゴンドラは営業しておらず雲海を見ることはできませんでした、、</p> <p>(雲海みたかった、、、、)</p> <p>実際の雲海はこんな感じだそうです。</p> <p> </p> <p><img src="https://www.fujimipanorama.com/UserFiles/Green/22yobi5/IMG_8666%20(800x533).jpg" /></p> <p><a href="https://www.fujimipanorama.com/">https://www.fujimipanorama.com/</a></p> <p> </p> <p>せっかく早起きしたのに、このまま帰るのも勿体なかったので、</p> <p>せめて高台で日の出でも見ようか!という話になり、</p> <p>歩いて山道を登り、高台を目指しました!</p> <p> </p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109180008.jpg" alt="f:id:kyoK:20201109180008j:plain" title="f:id:kyoK:20201109180008j:plain" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <p>少し歩いたら高台に着くだろうと安易な考えで登り始めましたが、</p> <p>そんなに甘くはありませんでした、、</p> <p> </p> <p>予定より時間がかかった事もあり、</p> <p>登っている途中で日の出を迎えることになりました。</p> <p><span style="text-decoration: underline;">完全に予定外</span>でしたが、<strong>これはこれで綺麗でした!!</strong></p> <p> </p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kyoK/20201109/20201109181538.jpg" alt="f:id:kyoK:20201109181538j:plain" title="f:id:kyoK:20201109181538j:plain" class="hatena-fotolife" itemprop="image" /></p> <p> </p> <p>結果的に、<strong>山道を往復で2時間程歩いてきました!</strong></p> <p>良い運動になったと言えばなりましたが、</p> <p>流石に少しだけ疲れましたね、、、</p> <p> </p> <p> </p> <h2><strong>最後に</strong></h2> <p>2日間通して、<span style="text-decoration: underline;">普段では体験できない事だらけ</span>の開発合宿になりました!!</p> <p>今回合宿に参加したメンバーの全員、</p> <p>開発合宿に行ってよかったと思っていましたし、</p> <p><strong>もし次回行く事があれば、次は場所をどこにしようか!</strong></p> <p><strong>次の合宿までにサービス改善案を出して、合宿内で開発しきろう!</strong></p> <p>などと行った案が出ています!</p> <p> </p> <p>ストレスフリーで作業効率がめちゃめちゃ上がりメンバーとも仲が深まる!</p> <p>開発合宿は良い事づくめです!</p> <p>普段、<strong>都内で開発業務に勤しんでいる</strong>方、</p> <p><strong>ストレスフリー</strong>で開発をしたい方、</p> <p>今まで以上に<strong>メンバーとの仲を深めたい</strong>方は</p> <p>是非、開発合宿に行ってみてはいかがでしょうか?</p> <p> </p> kyoK エンジニア歴1年の僕がドメイン駆動設計(DDD)を参考にLaravelのプロジェクトをフルリニューアルした話 hatenablog://entry/26006613640206908 2020-10-16T17:04:49+09:00 2020-10-16T17:04:49+09:00 こんにちは! はじめまして! 2020年7月からPIECE事業部でエンジニアをさせてもらっています。 野澤です。 今回、PIECEというサービスのリニューアルを担当させてもらったのでその時のことについて書きたいと思います! まだ若輩者なので至らない点が多々あると思いますが フルリニューアルってどんな事したんだろう〜? Hajimariのエンジニアはどんな仕事をしてるんだろう〜? って思った人はぜひ読んで見てください! ※ドメイン駆動設計の説明も書いたのですがボリュームが多くなってしまいました…ドメイン駆動設計について概要知りたいという方は是非読んでみてください。 クリーンアーキテクチャの説明や… <p>こんにちは!<br> はじめまして!<br> 2020年7月からPIECE事業部でエンジニアをさせてもらっています。<br> 野澤です。</p> <p><br>今回、<a href="https://crowd.itpropartners.com/piece/">PIECE</a>というサービスのリニューアルを担当させてもらったのでその時のことについて書きたいと思います!<br> まだ若輩者なので至らない点が多々あると思いますが</p> <p><br>フルリニューアルってどんな事したんだろう〜?<br> Hajimariのエンジニアはどんな仕事をしてるんだろう〜?<br> って思った人はぜひ読んで見てください!<br><br></p> <p>※<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計の説明も書いたのですがボリュームが多くなってしまいました…<br><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計について概要知りたいという方は是非読んでみてください。<br> クリーン<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>の説明や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E2%A5%C7%A5%EA%A5%F3%A5%B0">モデリング</a>のやり方などは説明していません。<br>ご了承ください。<br></p> <ul class="table-of-contents"> <li><a href="#PIECEリファクタリングプロジェクトの概要">PIECEリファクタリングプロジェクトの概要</a><ul> <li><a href="#PIECEとはどのようなサービスなのか">PIECEとはどのようなサービスなのか</a></li> <li><a href="#リニューアルの目的">リニューアルの目的</a></li> </ul> </li> <li><a href="#リニューアル施策">リニューアル施策</a><ul> <li><a href="#ドメイン駆動設計適用">ドメイン駆動設計適用</a></li> <li><a href="#技術的負債を返す既存のバグを改修">技術的負債を返す、既存のバグを改修</a></li> <li><a href="#DB制約の整備">DB制約の整備</a></li> <li><a href="#型を明示的に指定する">型を明示的に指定する</a></li> <li><a href="#コメントの強制">コメントの強制</a></li> <li><a href="#命名にこだわる">命名にこだわる</a></li> </ul> </li> <li><a href="#実際にリニューアルをしてみて">実際にリニューアルをしてみて</a></li> <li><a href="#最後に">最後に</a></li> <li><a href="#ドメイン駆動編">〜ドメイン駆動編〜</a></li> <li><a href="#ドメイン駆動設計を勉強するのに利用した本">ドメイン駆動設計を勉強するのに利用した本</a></li> <li><a href="#ドメイン駆動設計とは">ドメイン駆動設計とは</a></li> <li><a href="#層の責務について">層の責務について</a><ul> <li><a href="#プレゼンテーション層">プレゼンテーション層</a></li> <li><a href="#アプリケーション層">アプリケーション層</a></li> <li><a href="#ドメイン層">ドメイン層</a></li> <li><a href="#ドメインmodel">ドメイン(model)</a></li> <li><a href="#ドメインサービス">ドメインサービス</a></li> <li><a href="#リポジトリ">リポジトリ</a></li> <li><a href="#依存関係について">依存関係について</a></li> </ul> </li> <li><a href="#フォルダ構成について">フォルダ構成について</a></li> <li><a href="#ドメイン層-1">ドメイン層</a></li> <li><a href="#アプリケーション層-1">アプリケーション層</a></li> <li><a href="#ドメイン駆動設計の考え方を用いてリファクタリングした感想">ドメイン駆動設計の考え方を用いてリファクタリングした感想</a></li> </ul> <h3 id="PIECEリファクタリングプロジェクトの概要">PIECE<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>プロジェクトの概要<br /> </h3> <h5 id="PIECEとはどのようなサービスなのか">PIECEとはどのようなサービスなのか</h5> <p>PIECEとはスタートアップ向けマッチングサイト構築パッケージです。 特徴としては</p> <ol> <li>すでにマッチングサービスを運営するための機能が揃っているためスピード感があるビジネス展開が可能</li> <li>弊社サービスの<a href="https://itpropartners.com/">ITプロパートナーズ</a>で培ったノウハウがあるためマッチングビジネスに寄り添った開発、保守が可能</li> </ol> <p>です。 興味がある人は<a href="https://crowd.itpropartners.com/piece/">PIECEのホームページ</a>をぜひ見てみてください!</p> <h5 id="リニューアルの目的"> <br />リニューアルの目的</h5> <ul> <li>今後の保守、運用をしやすいように<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>する</li> <li>テストについて考えられていないのでテストできるように整える</li> <li>PIECEパッケージをもっと良くする<br> の3点が主にありました。<br><br><br></li> </ul> <h3 id="リニューアル施策">リニューアル施策</h3> <h5 id="ドメイン駆動設計適用"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計適用</h5> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計の考えを用いて層の責務をわけ、コントローラの肥大化を抑えるようにしました。<br> 適切な責務わけをしたことにより、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>の再利用性も上がりました。<br> それと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>という概念を用いてデータベース接続をORMに依存しないようにして、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>のインタフェースを用意することでテストのしやすさも改善することができました。<br> 今後はテストコードも書いていけるようにしていけたらいいなと思っています。<br><br></p> <h5 id="技術的負債を返す既存のバグを改修">技術的負債を返す、既存のバグを改修</h5> <p>bladeを直接使わずに、フロント側の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>をサーバーサイドで作っていた部分があったのでそこの分離や既存で発覚していたバグの改修をしました。<br><br></p> <h5 id="DB制約の整備">DB制約の整備</h5> <p>本来NOT NULLのところがNULL許容されていたり、外部キー制約が設定されていなかったり整っていませんでした。<br> カラムに対して一つずつ精査をして正しい制約を設定しました。<br><br></p> <h5 id="型を明示的に指定する">型を明示的に指定する</h5> <p>PIECEでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>のメソッドの引数、戻り値、プロパティの型を指定するようにしました。<br> 型指定をするメリットは<br></p> <ul> <li>想定外の引数を受け取った瞬間にエラーになるため、不具合を検知するタイミングが早くなる</li> <li>関数の意図がわかりやすくなる</li> <li>型が保証されるので関数の引数チェックが簡単になる<br> などがあります。<br> 安全な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を書くためには方が必要だと思ったのと、僕自身Hajimariでエンジニアになるまでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a>や<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%23">C#</a>など型があったほうが安心感もあったので明示的に型を指定するようにしました。<br><br></li> </ul> <h5 id="コメントの強制">コメントの強制</h5> <p>今まではメソッドコメントやクラスコメントが無かったのですがそれを書くようにしました。<br> それとコードの段落でのコメントを意識するようにしました。<br> 理想を言うと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を見ただけですぐコードを理解できればよいのですがそうはいかないときもあります。<br> そういうときにコメントがあれば早く理解でき、コードの改修がはかどります。<br><br></p> <h5 id="命名にこだわる"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>にこだわる</h5> <p>第<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BB%B0%BC%D4">三者</a>が見てすぐに分かるかどうかを重要視しました。<br> 良い<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BF%CC%BE">命名</a>のメソッドはメソッドの中身を見なくてもどんな処理をしているかわかります。<br><br><br></p> <h3 id="実際にリニューアルをしてみて">実際にリニューアルをしてみて</h3> <p>今までいかにも自分がやってきた感を出しながら記事を書いていたのですが、実はそうではなく僕含め4人で対応していました。<br> 見積もりやスケジューリング、タスクのチケットの割り振りなど経験がなく苦労しました。<br> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計も実務でやったことはほぼなく自分で本を読み、ネットで記事を読みPIECEに対してどのように適用したら良いのかを考え、フォルダ構成を考え色々試行錯誤しながら導入しました。<br> リニューアル中は上手くいかない部分がたくさんありましたがチームのみんなに協力してもらったり支えてもらったりで最終的にやり遂げることができました。<br> 良い製品を作るためにはスケジューリング能力であったり、チームでのコミュニケーション能力だったり「技術」以外の要素も多分に必要になってくるということを実感させられました。<br><br><br></p> <h3 id="最後に">最後に</h3> <p>このPIECEプロジェクトのリニューアルに携わることになったのは入社して1週間後くらいのことでした。<br> まだエンジニア組織としては未熟でたくさんの課題があると思っていますが未熟な分、積極的に行動すれば改善できることがたくさんあり、挑戦できることもたくさんあります。<br> Hajimariの理念に共感でき、能動的に仕事をしていきたい!というエンジニアさん是非一緒に仕事をしましょう!<br> Hajimariではエンジニアの採用も行っているので興味がある人は見てみてください。<br> <span style="font-size: 80%">※下に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計のちょっとした解説もあるので興味を持っていただいた方は見てみてください!</span></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fcompanies%2FHajimari%2Fprojects" title="株式会社Hajimari(旧:株式会社ITプロパートナーズ)の採用/求人一覧 - Wantedly" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/companies/Hajimari/projects">www.wantedly.com</a></cite></p> <p><br><br><br></p> <h3 id="ドメイン駆動編">〜<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動編〜</h3> <h3 id="ドメイン駆動設計を勉強するのに利用した本"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計を勉強するのに利用した本</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.shoeisha.co.jp%2Fbook%2Fdetail%2F9784798150727" title="ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 | 翔泳社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.shoeisha.co.jp/book/detail/9784798150727">www.shoeisha.co.jp</a></cite> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計について記事を調べたりすると抽象的な話が多い中、この本は具体的な実装例がたくさんありどのように実装していけばいいのかを知ることができる本です! 層の責務や概念はわかったけど具体的にどう書けばいいんだ!っていう人におすすめです。<br><br><br> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgihyo.jp%2Fbook%2F2017%2F978-4-7741-9087-7" title="現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://gihyo.jp/book/2017/978-4-7741-9087-7">gihyo.jp</a></cite> この本はプログラムを書くときに常に意識しておきたいことについてのことがたくさん書いてあり、かつ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>モデルの考え方や画面遷移やweb <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>についてにも触れられており、<br> 本当に現場で役に立つことが書いてありました。<br> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計に問わずおすすめの本です。<br><br><br> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.shoeisha.co.jp%2Fbook%2Fdetail%2F9784798161495" title="「実践ドメイン駆動設計」から学ぶDDDの実装入門 | 翔泳社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.shoeisha.co.jp/book/detail/9784798161495">www.shoeisha.co.jp</a></cite> この本も具体的な実装例が書いてありとても参考にしました。<br><br><br> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgihyo.jp%2Fmagazine%2Fwdpress%2Farchive%2F2019%2Fvol113" title="WEB+DB PRESS Vol.113" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://gihyo.jp/magazine/wdpress/archive/2019/vol113">gihyo.jp</a></cite> 雑誌なのですがそこに書いてある<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計がとても参考にしました。 「集約」という言葉について「そういうことだったのか!」となった本です。 ページ数は少ないですが体系的にまとめられておりその少ないページに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計の情報がかなり凝縮されていて腑に落ちる部分がたくさんありとても良かったです。 <br><br></p> <h3 id="ドメイン駆動設計とは"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計とは</h3> <p>僕自身うまく言葉にできなかったので<a class="keyword" href="http://d.hatena.ne.jp/keyword/Wikipedia">Wikipedia</a>で調べてみたのですが<br> 【ソフトウェアの設計手法であり、「複雑な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>の設計は、モデルベースで行うべき」であり、また「大半のソフトウェアプロジェクトでは、システムを実装するための特定の技術ではなく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>そのものと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>のロジックに焦点を置くべき」であるとする】 と書いてありました…<br> 抽象的すぎて分かりづらいですよね…<br> 僕もまだ勉強の途中なので理解不足な面は多大にありますが僕自身の感覚だと「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>の開発の効率や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>の再利用性を最大限上げることができる設計手法、業務(ビジネス)を中心にそえて開発を進めていくための設計手法」となります。<br> 機能を開発するときも、改修するときも<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>に注力します。<br> そこで<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>や<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を意識させないようにします。<br> そのようにすることで業務の発展に合わせてソフトウェアを成長させることに集中することができます。<br><br><br></p> <h3 id="層の責務について">層の責務について</h3> <p>この記事を読んで概念がなんとなくわかるようになっていただけたら嬉しいです。 具体的な実装、コードについては別途また他の記事で書いたりしようと思います。</p> <h5 id="プレゼンテーション層">プレゼンテーション層</h5> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>のコントローラがこの層に属していると考えます。<br> この層は下記アプリケーション層に依存し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>を実現します。<br> その他http系の処理や<a class="keyword" href="http://d.hatena.ne.jp/keyword/Cookie">Cookie</a>、セッション関連の処理が責務となります。<br> <b>実装例.</b></p> <pre class="code lang-php" data-lang="php" data-unlink>class UserController { /** * コンストラクタ * * <span class="synPreProc">@param </span>\Application\User\UserApplicationService $service */ public function <span class="synStatement">__construct</span>(UserApplicationService $service) { $this-<span class="synError">&gt;</span>service = $service; } /** * ユーザー情報参照 * * <span class="synPreProc">@param </span>string $id * <span class="synPreProc">@return </span>\Illuminate\View\View */ public function show(string $id): View { $user = $this-<span class="synError">&gt;</span>service-<span class="synError">&gt;</span>show($id); return view('user.show')-<span class="synError">&gt;</span>with([ 'user' =<span class="synError">&gt;</span> $user ]); } } </pre> <p><br><br></p> <h5 id="アプリケーション層">アプリケーション層</h5> <p>アプリケーション層は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>管理や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>層、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を使った<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>の実現が責務です。<br> ここに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>は書きません。<br> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>はmodelかDomainServiceにまとめたあと、そのメソッドを呼ぶようにします。<br> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>を組み立てて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>を実現します。<br> それと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動特有のルールではないのですがPIECEではこの層で直接ORMを利用してDB接続に関する処理を書いてはいけないという決まりにしました。<br> DB接続に関してはただ保存するだけ、ただ更新するだけであれば<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>のupdateOrInsertメソッドを直接呼び出して良い、そこに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>が入ってきた場合(データの加工をしたあとにデータ保存など)はDomainServiceを通じてDB接続をするようにします。<br> これらはApplicationServiceクラスに記述します。<br> <b>実装例.</b></p> <pre class="code lang-php" data-lang="php" data-unlink>class UserApplicationService { /** * ユーザー情報参照 * * <span class="synPreProc">@param </span>string $id * <span class="synPreProc">@param </span>integer $companyId * <span class="synPreProc">@return </span>\Domain\Models\User\User */ public function show(string $id): User { $userRepository = new UserRepository(); $user = $userRepository-<span class="synError">&gt;</span>get($id); return $user; } } </pre> <h5 id="ドメイン層"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>層</h5> <h5 id="ドメインmodel"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>(model)</h5> <p>基本<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>はここに書くことになります。<br> 本来<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計において<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>のモデルと<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a>のモデルは意味合いが違うのですが、今回PIECEでは同一のものととして考えることにしました。<br> ですので<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>=モデル=Entityといった感じで捉えるようにしています。<br> <b>実装例.</b></p> <pre class="code lang-php" data-lang="php" data-unlink>class User extends Model { /** * フルネーム取得 * * <span class="synPreProc">@return </span>void */ public function getFullNameAttribute(): string { return $this-<span class="synError">&gt;</span>last_name. &quot; &quot; .$this-<span class="synError">&gt;</span>first_name; } // Userクラスが持つデータに対する業務ロジックを実装 } </pre> <p><br><br></p> <h5 id="ドメインサービス"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>サービス</h5> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>(model)で書くべきではない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>を書くときにここに書きます。<br> modelに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>を書くのに違和感があるとき(クラスのプロパティに対するロジックではない)や複数の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>が関わってくるロジックのときなどに記述します。<br> 例えば「ログイン可能かどうか」というメソッドは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>に置くべきではないと考えます。<br> 1ユーザーが自分がログイン可能かどうか自分自身で確認していくのは違和感を感じます。。<br> ですので「ログイン可能かどうか」というメソッドは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>サービスに置くのが良いと思います。<br> <b>実装例.</b></p> <pre class="code lang-php" data-lang="php" data-unlink>class UserDomainService { /** * ユーザー重複チェック * * <span class="synPreProc">@param </span>User $user * <span class="synPreProc">@return </span>void */ public function exists(User $user) : User { $userRepository = new UserRepository(); $user = userRepository-<span class="synError">&gt;</span>get($user-<span class="synError">&gt;</span>id); return <span class="synStatement">empty</span>($user-<span class="synError">&gt;</span>id) === false; } } </pre> <p><br><br></p> <h5 id="リポジトリ"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a></h5> <p>データのやり取りの際は必ずこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>が呼ばれます。 ここのメソッドはDBの接続に関するものだったら内部でORMを利用します。<br> DBへの接続など意識することなく、データの入出力を実現します。<br> ここはDBへの接続だけではなく、例えばNoSQLを利用するようにしたりすることもできます。<br> ※<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計において<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>のインタフェースを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>層、実装クラスをインフラスト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>チャ層と分類したりするのですが、フォルダの構成上の事を考えて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>も<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>層の要素とみなしています。<br> <b>実装例.</b></p> <pre class="code lang-php" data-lang="php" data-unlink>class UserRepository { /** * データ取得 * * <span class="synPreProc">@param </span>string $primaryKey * <span class="synPreProc">@return </span>void */ public function get(string $id): User { $user = User::find($id); return $user ?? new User(); } /** * データ更新or追加 * * <span class="synPreProc">@param </span>User $user * <span class="synPreProc">@return </span>void */ public function updateOrInsert(User $user): void { $user-<span class="synError">&gt;</span>save(); } } </pre> <p><br><br><br></p> <h5 id="依存関係について">依存関係について</h5> <p>PIECEではプレゼンテーション層がアプリケーション層に依存し、アプリケーション層が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>層に依存する形にしました。<br> もっとわかりやすく言い換えるとControlllerがApplicationServiceのメソッドを呼びます。<br><br><br></p> <h3 id="フォルダ構成について">フォルダ構成について</h3> <h3 id="ドメイン層-1"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>層</h3> <pre class="code lang-php" data-lang="php" data-unlink>package │ ├── Domain │ │ └── Models │ │ ├── Company │ │ ├── User </pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>層に関しては上記のようにModelsの下にmodelの名前のフォルダを作り、その中に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>(model)、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>サービス、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>インタフェース、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を格納しています。<br> フォルダ構成についてはいろいろな考え方があると思いますがPIECEでは各要素を一つのフォルダに加えることで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を見つけやすくなると考えてそのような構成にしました。<br> 例えばUserに関する機能を作るときはUserの下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>サービスを見れば再開発しなくても済むかもしれないと予想することが用意になります。<br><br></p> <h3 id="アプリケーション層-1">アプリケーション層</h3> <pre class="code lang-php" data-lang="php" data-unlink>packages │ ├── Application │ │ ├── Company │ │ ├── User </pre> <p>アプリケーション層に関しては上記のように構成しています。<br> CompanyやUserというフォルダにはApplicationServiceと<a class="keyword" href="http://d.hatena.ne.jp/keyword/DTO">DTO</a>のファイルが格納されています。<br> <a class="keyword" href="http://d.hatena.ne.jp/keyword/DTO">DTO</a>とは【Data Transfer Object】のことで、関連するデータを一つにまとめた、データを運ぶだけのオブジェクトです。<br> PIECEではApplicationServiceのメソッドでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>(model)か<a class="keyword" href="http://d.hatena.ne.jp/keyword/DTO">DTO</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>をControllerに返すようにしました。<br></p> <h3 id="ドメイン駆動設計の考え方を用いてリファクタリングした感想"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計の考え方を用いて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>した感想</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計は短期間での<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>や新規開発などスピード感を求められる場面ではあまり向いていないと思いました。<br> 実際に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計の考え方を取り入れたときにネックだったなと思ったのは<br></p> <ul> <li>責務わけをしたことによりファイルの数が多くなり対応に時間がかかった。</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計にどこまで乗っ取るのか決めるのに時間がかかった。(「entitiy」、「valueobject」、「仕様」、「集約」、「ファクトリー」など<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計で重要な概念があるがスケジュールのことや今後のことを考えて使用しなかった)</li> <li>チームで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動の概念の理解や<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計に基づくルールに則ってちゃんとコードに起こせるようになるまでのコストが高いと感じた。</li> <li>集約の概念が難しい。「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>でのやり取りの基本単位が集約」、「不変条件を維持する単位」など頭でわかっていても<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E2%A5%C7%A5%EA%A5%F3%A5%B0">モデリング</a>を時間をかけてやらないと上手くいかない部分が多々出てきた。<br></li> </ul> <p>という4点です。<br> プロジェクトの規模にもよりますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>をするために時間をたくさんかけることができる状態であり、<br> 入念に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>エキスパートの人に話を聞いて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E2%A5%C7%A5%EA%A5%F3%A5%B0">モデリング</a>して実装に起こしていくというふうにすすめていかないとレベルの高い<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計は難しいと思いました。<br> ただし軽量<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C9%A5%E1%A5%A4%A5%F3">ドメイン</a>駆動設計でも多分にコードのみやすさであったり<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%ED%A5%B8%A5%C3%A5%AF">ビジネスロジック</a>の再利用性は上がったので良かったと思っています。<br><br><br></p> nozawa_taira レスポンス・ヘッダフィールドからnginxのバージョンを消す...から学べること hatenablog://entry/26006613633747221 2020-09-30T16:34:20+09:00 2020-09-30T16:34:20+09:00 こんにちは、もうすっかり秋ですね。エンジニアの三宅です。 秋といえばnginxですね。 今回は、nginxのバージョンがHTTPのレスポンス・ヘッダフィールドにdefaultで設定されるという噂を耳にしたので確認します。 nginxのバージョン設定 nginxのコードを読む main関数のループ responseに設定しているのは何処 初期値はONなのか? response送信 おわりに nginxのバージョン設定 nginxに特に設定をしていないとHTTPのレスポンスヘッダーにサーバーアプリケーションのバージョンが表示されてしまいます。 あ、見えちゃってる…! レスポンスヘッダーにアプリケー… <div> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20200928/20200928214516.png" alt="f:id:kumamon_engineer:20200928214516p:plain" title="f:id:kumamon_engineer:20200928214516p:plain" class="hatena-fotolife" itemprop="image" /></p> </div> <p> </p> <p>こんにちは、もうすっかり秋ですね。エンジニアの三宅です。</p> <p>秋といえばnginxですね。</p> <p> 今回は、nginxのバージョンがHTTPのレスポンス・ヘッダフィールドにdefaultで設定されるという噂を耳にしたので確認します。</p> <p> </p> <ul class="table-of-contents"> <li><a href="#nginxのバージョン設定">nginxのバージョン設定</a></li> <li><a href="#nginxのコードを読む">nginxのコードを読む</a><ul> <li><a href="#main関数のループ">main関数のループ</a></li> <li><a href="#responseに設定しているのは何処"> responseに設定しているのは何処</a></li> <li><a href="#初期値はONなのか">初期値はONなのか? </a></li> <li><a href="#response送信">response送信</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> </ul> <p> </p> <h3 id="nginxのバージョン設定">nginxのバージョン設定</h3> <div style="-en-clipboard: true;">nginxに特に設定をしていないとHTTPのレスポンスヘッダーにサーバーアプリケーションのバージョンが表示されてしまいます。</div> <div> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20200928/20200928212914.png" alt="f:id:kumamon_engineer:20200928212914p:plain" title="f:id:kumamon_engineer:20200928212914p:plain" class="hatena-fotolife" itemprop="image" /></p> </div> <div>あ、見えちゃってる…!</div> <div> </div> <div>レスポンスヘッダーにアプリケーションのバージョンがあると何が良くないかというのは、HTTPの<a class="keyword" href="http://d.hatena.ne.jp/keyword/RFC">RFC</a>には以下の記述があります。<br /> <div> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftools.ietf.org%2Fhtml%2Frfc2616%23section-14.38" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://tools.ietf.org/html/rfc2616#section-14.38">tools.ietf.org</a></cite></p>   <div> <blockquote> <div>      Note: Revealing the specific software version of the server might</div> <div>      allow the server machine to become more vulnerable to attacks</div> <div>      against software that is known to contain security holes. Server</div> <div>      implementors are encouraged to make this field a configurable</div> <div>      option.</div> <div> </div> <div>      注意: サーバーの特定のソフトウェアバージョンを明らかにすると、</div> <div>      以下のようになる場合があります。</div> <div>      サーバマシンが攻撃に対してより脆弱になることを可能にします。</div> <div>      <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%AD%A5%E5%A5%EA%A5%C6%A5%A3%A5%DB%A1%BC%A5%EB">セキュリティホール</a>が含まれていることが知られているソフトウェア</div> <div>       に対してサーバ実装者は、このフィールドを設定可能なオプションを</div> <div>       使用します。</div> </blockquote> <p> </p> </div> </div> </div> <p>特定のバージョンで動いている事を公開していることは、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%AD%A5%E5%A5%EA%A5%C6%A5%A3%A5%DB%A1%BC%A5%EB">セキュリティホール</a>が含まれたバージョンを使っていた場合、攻撃対象になってしまいます…ということですね。</p> <p>まぁ、見せて得することはあまり無さそうですね。</p> <p style="font-size: 13.3333px; letter-spacing: normal; orphans: 2; widows: 2; word-spacing: 0px; --inversion-type-color: simple;"><span style="color: #000000; --inversion-type-color: simple;"> </span></p> <p>nginxの方ではconfig設定で以下を定義する事で消すことが出来るそうです。</p> <p> </p> <div> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Module ngx_http_core_module" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnginx.org%2Fen%2Fdocs%2Fhttp%2Fngx_http_core_module.html%3F_ga%3D2.200437165.2067419292.1601274471-782795405.1599808555%23server_tokens" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://nginx.org/en/docs/http/ngx_http_core_module.html?_ga=2.200437165.2067419292.1601274471-782795405.1599808555#server_tokens">nginx.org</a></cite></p> </div> <div> <blockquote style="box-sizing: inherit; margin: 1.5em 0px; padding: 1em 0px 1em 1em; border-left: 5px solid #dddddd; color: #777777; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial;"> <p style="box-sizing: inherit; margin: 0px; padding: 0px; line-height: 1.9;">Syntax: server_tokens on | off | build | string;<br style="box-sizing: inherit;" />Default: server_tokens on;<br style="box-sizing: inherit;" />Context: http, server, location<br style="box-sizing: inherit;" /> <br style="box-sizing: inherit;" />Enables or disables emitting nginx version on error pages and in the “Server” response header field.</p> </blockquote> </div> <div>conf用ファイル</div> <div style="box-sizing: border-box; padding: 8px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 12px; color: #333333; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; background-color: #fbfaf8; border: 1px solid rgba(0, 0, 0, 0.15); -en-codeblock: true;"> <div>http {</div> <div>    server_tokens off;</div> <div>    ...</div> <div>}</div> </div> <div> </div> <div>これでnginxを再起動</div> <div> </div> <div>はい、消えました!</div> <div> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20200928/20200928213019.png" alt="f:id:kumamon_engineer:20200928213019p:plain" title="f:id:kumamon_engineer:20200928213019p:plain" class="hatena-fotolife" itemprop="image" /></p> </div> <div>ありがとうございました!お疲れさまでした!</div> <div> </div> <div> </div> <div> </div> <div> </div> <div> </div> <div><strong><em>fin...?</em></strong></div> <div> </div> <div> </div> <div> </div> <div> </div> <div>さて、これってnginxのソフトウェア上では何が起きてるのでしょうか..。</div> <div>今回はnginxのコードを読んで理解を深めていこうと思います。</div> <div> </div> <h3 id="nginxのコードを読む">nginxのコードを読む</h3> <div>nginxのコードは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Github">Github</a>で見れます!ただここで管理されている訳ではなく、</div> <div>公式のコードを1時間おきに更新しているミラーです。</div> <div>参照するには十分ですね。</div> <div> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="nginx/nginx" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fnginx%2Fnginx" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://github.com/nginx/nginx">github.com</a></cite></p> </div> <div> </div> <div>さて、nginxは<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>で書かれていますので、コードを読むには最低限の<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>の知識は必要になります。</div> <h4 id="main関数のループ">main関数のループ</h4> <div>まずはmain関数を確認します!</div> <div> </div> <div>ありました、ありました!</div> <div><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>のソフトウェアはエントリーポイントとしてmain関数が呼ばれます。</div> <div> <p><a href="https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/core/nginx.c#L195">https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/core/nginx.c#L195</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="kt" style="box-sizing: inherit; color: #ebd247;">int</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_cdecl</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">main</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="kt" style="box-sizing: inherit; color: #ebd247;">int</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">argc</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="kt" style="box-sizing: inherit; color: #ebd247;">char</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">*</span><span class="k" style="box-sizing: inherit; color: #ebd247;">const</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">*</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">argv</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="err" style="box-sizing: inherit; border-bottom: 2px dotted #c01b1b; color: #dddddd;"> </span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="err" style="box-sizing: inherit; border-bottom: 2px dotted #c01b1b; color: #dddddd;"> </span> <span class="err" style="box-sizing: inherit; border-bottom: 2px dotted #c01b1b; color: #dddddd;">…</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span></pre> </div> </div> </div> <div>この関数抜けるとソフトウェアが終了してしまうため、どこかに無限ループがあるはずです。</div> <div> <div> </div> <div>ここでプロセス管理してそうですね</div> </div> <div><a href="https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/core/nginx.c#L378">https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/core/nginx.c#L378</a></div> <div>  <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="k" style="box-sizing: inherit; color: #ebd247;">if</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_process</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">==</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_PROCESS_SINGLE</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="err" style="box-sizing: inherit; border-bottom: 2px dotted #c01b1b; color: #dddddd;"> </span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_single_process_cycle</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">cycle</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span> <span class="err" style="box-sizing: inherit; border-bottom: 2px dotted #c01b1b; color: #dddddd;"> </span> <span class="err" style="box-sizing: inherit; border-bottom: 2px dotted #c01b1b; color: #dddddd;"> </span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">else</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="err" style="box-sizing: inherit; border-bottom: 2px dotted #c01b1b; color: #dddddd;"> </span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_master_process_cycle</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">cycle</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> </pre> </div> </div> </div> <div> </div> <div><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">さて、 </span><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_master_process_cycle</code><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"> 関数を見てみます。</span><br style="box-sizing: inherit; color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial;" /><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>では関数を呼び出すためにはheaderファイルで定義したものをincludeすることで出来ます。</span><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_master_process_cycle</code><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">が定義されているのは</span><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_process_cycle.h</code><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">で、</span><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">これは</span><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_core.h</code><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">でincludeされており、</span><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">nginx.c</code><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">では</span><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_core.h</code><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">をincludeしています。</span></div> <div><a href="https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/core/nginx.c#L9">https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/core/nginx.c#L9</a></div> <div> </div> <div>あれ<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_process_cycle.h</code>が2つあるな…</div> <div> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20200928/20200928214007.png" alt="f:id:kumamon_engineer:20200928214007p:plain" title="f:id:kumamon_engineer:20200928214007p:plain" class="hatena-fotolife" itemprop="image" /></p> </div> <p><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_process_cycle.h</code>ファイルはOSが<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>系か<a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>系かで分かれるようですね。OSによって中の実装が変わる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%B9%A5%C6%A5%E0%A5%B3%A1%BC%A5%EB">システムコール</a>系の処理をラッピングする為に、同名でOS毎に分けて書かれているんですね</p> <div> </div> <div>どっちが呼び出されるか…</div> <div>【<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>_DEPSとして定義】</div> <div><a href="https://github.com/nginx/nginx/blob/38196b8ba63f04830db0b8793eca738e162c6d8e/auto/sources#L148">https://github.com/nginx/nginx/blob/38196b8ba63f04830db0b8793eca738e162c6d8e/auto/sources#L148</a></div> <div> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="text"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>_DEPS="$CORE_DEPS $EVENT_DEPS \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_time.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_errno.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_alloc.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_files.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_channel.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_shmem.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_process.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_setaffinity.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_setproctitle.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_atomic.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/gcc">gcc</a>_atomic_<a class="keyword" href="http://d.hatena.ne.jp/keyword/x86">x86</a>.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_thread.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_socket.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_os.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_user.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_dlopen.h \ src/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/unix">unix</a>/ngx_process_cycle.h" </pre> </div> </div> </div> <div>【WIN32_DEPSとして定義】</div> <div><a href="https://github.com/nginx/nginx/blob/38196b8ba63f04830db0b8793eca738e162c6d8e/auto/sources#L228">https://github.com/nginx/nginx/blob/38196b8ba63f04830db0b8793eca738e162c6d8e/auto/sources#L228</a><br /> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="text"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;">WIN32_DEPS="$CORE_DEPS $EVENT_DEPS \ src/os/win32/ngx_win32_config.h \ src/os/win32/ngx_time.h \ src/os/win32/ngx_errno.h \ src/os/win32/ngx_alloc.h \ src/os/win32/ngx_files.h \ src/os/win32/ngx_shmem.h \ src/os/win32/ngx_process.h \ src/os/win32/ngx_atomic.h \ src/os/win32/ngx_thread.h \ src/os/win32/ngx_socket.h \ src/os/win32/ngx_os.h \ src/os/win32/ngx_user.h \ src/os/win32/ngx_dlopen.h \ src/os/win32/ngx_process_cycle.h" </pre> </div> </div> <p style="box-sizing: inherit; margin: 0px; padding: 0px; line-height: 1.9; color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial;"><span style="color: #3d4245; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;">この設定はmakeファイルにあるはずです。</span></p> </div> <div><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%B8%C0%B8%EC">C言語</a>はmakeコマンドで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>する場合、makeファイルに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>設定を定義します。</div> <div> </div> <div>ここの配下でOS毎の定義をまとめています</div> <div><a href="https://github.com/nginx/nginx/tree/master/auto/os">https://github.com/nginx/nginx/tree/master/auto/os</a></div> <div> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20200929/20200929092257.png" alt="f:id:kumamon_engineer:20200929092257p:plain" title="f:id:kumamon_engineer:20200929092257p:plain" class="hatena-fotolife" itemprop="image" /></p> </div> <div>OS毎に色々やってますね</div> <div><a href="https://github.com/nginx/nginx/blob/master/auto/os/conf">https://github.com/nginx/nginx/blob/master/auto/os/conf</a></div> <div> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="text"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;">case "$NGX_PLATFORM" in<br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/FreeBSD">FreeBSD</a>:*)   . auto/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/freebsd">freebsd</a>   ;;    <a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>:*)   . auto/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/linux">linux</a>   ;;   <a class="keyword" href="http://d.hatena.ne.jp/keyword/SunOS">SunOS</a>:*)   . auto/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/solaris">solaris</a>   ;; <a class="keyword" href="http://d.hatena.ne.jp/keyword/Darwin">Darwin</a>:*)   . auto/os/<a class="keyword" href="http://d.hatena.ne.jp/keyword/darwin">darwin</a>   ;;   win32)   . auto/os/win32   ;; </pre> </div> </div> </div> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>はここでWIN32_DEPSを設定</p> <p><a href="https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/win32#L9">https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/win32#L9</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="text"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;">CORE_DEPS="$WIN32_DEPS"</pre> </div> </div> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>はここで<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>_DEPSを設定</p> <p><a href="https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/linux#L9">https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/linux#L9</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="text"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;">CORE_DEPS="$<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>_DEPS $<a class="keyword" href="http://d.hatena.ne.jp/keyword/LINUX">LINUX</a>_DEPS"</pre> </div> </div> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Solaris">Solaris</a>だったらここ(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Linux">Linux</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/Solaris">Solaris</a>は<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>系なので<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>_DEPSは共通ですね)</p> <p><a href="https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/solaris#L9">https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/os/solaris#L9</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="text"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;">CORE_DEPS="$<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>_DEPS $<a class="keyword" href="http://d.hatena.ne.jp/keyword/SOLARIS">SOLARIS</a>_DEPS"</pre> </div> </div> <p> <span style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;">makeファイルでCORE_DEPSを利用</span></p> <p><a href="https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/make#L56">https://github.com/nginx/nginx/blob/c85d6fec217d1b17291779542de20ad77ae68661/auto/make#L56</a></p> <div style="box-sizing: border-box; padding: 8px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 12px; color: #333333; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; background-color: #fbfaf8; border: 1px solid rgba(0, 0, 0, 0.15); -en-codeblock: true;">ngx_deps=`echo $CORE_DEPS $NGX_AUTO_CONFIG_H $NGX_PCH \<br /> | <a class="keyword" href="http://d.hatena.ne.jp/keyword/sed">sed</a> -e "s/ *\([^ ][^ ]*\)/$ngx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/regex">regex</a>_cont\1/g" \<br /> -e "s/\//$ngx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/regex">regex</a>_dirsep/g"`</div> <div> </div> <p>…ということで、ngx_master_process_cycleは<a class="keyword" href="http://d.hatena.ne.jp/keyword/UNIX">UNIX</a>系か<a class="keyword" href="http://d.hatena.ne.jp/keyword/Windows">Windows</a>系かで別れていますよ…と。</p> <p> </p> <p>差分を見ると関数の中身、初っ端から全然違いますね。。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20200929/20200929083312.png" alt="f:id:kumamon_engineer:20200929083312p:plain" title="f:id:kumamon_engineer:20200929083312p:plain" class="hatena-fotolife" itemprop="image" /></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>(左側)はHANDLEとか使ってますね。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>でスレッドやイベント生成した時に返ってくるポインタの型ですね。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/windows">windows</a>プログラムの基本ですね、懐かしい</p> <p>(そして、今はweb上にこんなドキュメントがあるのか...羨ましい)</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Handles and Objects - Win32 apps" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fwindows%2Fwin32%2Fsysinfo%2Fhandles-and-objects" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://docs.microsoft.com/en-us/windows/win32/sysinfo/handles-and-objects">docs.microsoft.com</a></cite></p> <p> </p> <p>そうそうmain関数の中にある無限ループを探すのが目的でした…</p> <p> </p> <p>ありました!このfor文の中をグルグル回ってるんですね、nginxは..</p> <p><a href="https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/os/unix/ngx_process_cycle.c#L139">https://github.com/nginx/nginx/blob/9c3ac44de268f0cf057bc5dd67929e74c9bbc3e3/src/os/unix/ngx_process_cycle.c#L139</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="k" style="box-sizing: inherit; color: #ebd247;">for</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">;;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> ・・・ <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> </pre> </div> </div> <p style="box-sizing: inherit; margin: 0px; padding: 0px; line-height: 1.9; color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial;">本筋を見失ってしまいましたが、この無限ループ内でworkerプロセスのイベント監視して非同期にhttp通信を管理しているんですね。。。</p> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>はこの図がわかりやすいです!</p> <div> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20200929/20200929055307.png" alt="f:id:kumamon_engineer:20200929055307p:plain" title="f:id:kumamon_engineer:20200929055307p:plain" class="hatena-fotolife" itemprop="image" /></p> </div> <h4 id="responseに設定しているのは何処"> responseに設定しているのは何処</h4> <p>では今回のレスポンス・ヘッダーにサーバーアプリケーションのバージョンを設定している部分はソフトウェアではどうしているのでしょうか。</p> <p>  </p> <div> <p>まずは、NGINXのバージョンを定義している部分を見つけました</p> <p><a href="https://github.com/nginx/nginx/blob/554916301c424f02b1cabc073845b64f8681099b/src/core/nginx.h#L14">https://github.com/nginx/nginx/blob/554916301c424f02b1cabc073845b64f8681099b/src/core/nginx.h#L14</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="text"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;">#define NGINX_VERSION "1.19.3" #define NGINX_VER "nginx/" NGINX_VERSION</pre> </div> </div> </div> <p>恐らく、この定義を元に設定している部分があるのでしょう</p> <p> </p> <p>ありますね!<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_http_header_filter_module.c</code>ファイルですね。ヘッダーを組み立てる処理が書かれていそうです。 </p> <p><a href="https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L50">https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L50</a></p> <div> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="k" style="box-sizing: inherit; color: #ebd247;">static</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">u_char</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">[]</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="s" style="box-sizing: inherit; color: #41b7d7;">"Server: nginx"</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">CRLF</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">static</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">u_char</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_full_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">[]</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="s" style="box-sizing: inherit; color: #41b7d7;">"Server: "</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGINX_VER</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">CRLF</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">static</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">u_char</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_build_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">[]</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="s" style="box-sizing: inherit; color: #41b7d7;">"Server: "</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGINX_VER_BUILD</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">CRLF</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> </pre> </div> </div> </div> <p><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ngx_http_server_full_string</code>がversion付きの文字列ですね</p> <p>ここで使ってますね</p> <p><a href="https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L452">https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L452</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"> <span class="k" style="box-sizing: inherit; color: #ebd247;">if</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">clcf</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">server_tokens</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">==</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_HTTP_SERVER_TOKENS_ON</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">p</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_full_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">len</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">sizeof</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_full_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">-</span> <span class="mi" style="box-sizing: inherit; color: #a980f5;">1</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">else</span> <span class="nf" style="box-sizing: inherit; color: #8bdf4c;">if</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">clcf</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">server_tokens</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">==</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_HTTP_SERVER_TOKENS_BUILD</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">p</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_build_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">len</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">sizeof</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_build_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">-</span> <span class="mi" style="box-sizing: inherit; color: #a980f5;">1</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">else</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">p</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">len</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">sizeof</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_server_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">-</span> <span class="mi" style="box-sizing: inherit; color: #a980f5;">1</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">b</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">last</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_cpymem</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">b</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">last</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">p</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">len</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span></pre> </div> </div> <p style="box-sizing: inherit; margin: 0px; padding: 0px; line-height: 1.9; color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial;"><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">if (clcf-&gt;server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {</code> で判定されてますね。<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">b-&gt;last</code>にcopyされてます。</p> <p style="box-sizing: inherit; margin: 1.5em 0px 0px; padding: 0px; line-height: 1.9; color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial;">ちなみに<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_cpymem</code>は<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">memcpy</code>をラッピングしたマクロになります。<br style="box-sizing: inherit;" /><a href="https://github.com/nginx/nginx/blob/6c3838f9ed45f5c2aa6a971a0da3cb6ffe45b61e/src/core/ngx_string.h#L107">https://github.com/nginx/nginx/blob/6c3838f9ed45f5c2aa6a971a0da3cb6ffe45b61e/src/core/ngx_string.h#L107</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="cp" style="box-sizing: inherit; color: #9dabae;">#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n)) </span></pre> </div> </div> <p><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">clcf</code> はなんでしょう。<br style="box-sizing: inherit;" /><a href="https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L280">https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L280</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="n" style="box-sizing: inherit; color: #e3e3e3;">clcf</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_get_module_loc_conf</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_core_module</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span> </pre> </div> </div> <p>関数名を見る限り、ローカルの設定のようです。</p> <p>構造体の定義<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">struct ngx_http_core_loc_conf_s</code>はここにあります。<br style="box-sizing: inherit;" />設定を集約した定義のようです。<a href="https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.h#L397">https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.h#L397</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="k" style="box-sizing: inherit; color: #ebd247;">struct</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_core_loc_conf_s</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">...</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_uint_t</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">server_tokens</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="cm" style="box-sizing: inherit; color: #9dabae;">/* server_tokens */</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">...</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span></pre> </div> </div> <h4 id="初期値はONなのか">初期値はONなのか? </h4> <p><a href="https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.c#L3794">https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.c#L3794</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_conf_merge_uint_<a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a></span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">conf</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">server_tokens</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">prev</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">server_tokens</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_HTTP_SERVER_TOKENS_ON</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span> <span class="c1" style="box-sizing: inherit; color: #9dabae;">// 定義</span> <span class="cp" style="box-sizing: inherit; color: #9dabae;">#define ngx_conf_merge_<a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a>(conf, prev, default) \ if (conf == NGX_CONF_UNSET) { \ conf = (prev == NGX_CONF_UNSET) ? default : prev; \ }</span></pre> </div> </div> <p>確かにdefaultはONで設定されていました!</p> <p>offをconfigに設定すると<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">NGX_HTTP_SERVER_TOKENS_OFF</code>が設定されることも確認できます。</p> <p><a href="https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.c#L125">https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.c#L125</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="k" style="box-sizing: inherit; color: #ebd247;">static</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_conf_<a class="keyword" href="http://d.hatena.ne.jp/keyword/enum">enum</a>_t</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_core_server_tokens</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">[]</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="s" style="box-sizing: inherit; color: #41b7d7;">"off"</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">),</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_HTTP_SERVER_TOKENS_OFF</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">},</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="s" style="box-sizing: inherit; color: #41b7d7;">"on"</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">),</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_HTTP_SERVER_TOKENS_ON</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">},</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="s" style="box-sizing: inherit; color: #41b7d7;">"build"</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">),</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_HTTP_SERVER_TOKENS_BUILD</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">},</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_null_string</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="mi" style="box-sizing: inherit; color: #a980f5;">0</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">};</span></pre> </div> </div> <p>ということで...</p> <div style="box-sizing: border-box; padding: 8px; font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; font-size: 12px; color: #333333; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; background-color: #fbfaf8; border: 1px solid rgba(0, 0, 0, 0.15); -en-codeblock: true;"> <div>http {</div> <div>    server_tokens off;</div> <div>    ...</div> <div>}</div> </div> <p>を設定すると、condig設定でoff→<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">NGX_HTTP_SERVER_TOKENS_OFF</code>に置き換わり、<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">header_filter_module</code>の分岐にてどの文字列をcopyするかが決まります。 </p> <h4 id="response送信">response送信</h4> <p>最後、送信するところですね</p> <p><a href="https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.c#L1733">https://github.com/nginx/nginx/blob/b82c08f6102d65a5e5902e6fa85082e184a75003/src/http/ngx_http_core_module.c#L1733</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_int_t</span> <span class="nf" style="box-sizing: inherit; color: #8bdf4c;">ngx_http_send_response</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_request_t</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">*</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_uint_t</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">status</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_str_t</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">*</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ct</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_complex_<a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a>_t</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">*</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">cv</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">...</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">rc</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_send_header</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">...</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="c1" style="box-sizing: inherit; color: #9dabae;">// ヘッダー生成</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_int_t</span> <span class="nf" style="box-sizing: inherit; color: #8bdf4c;">ngx_http_send_header</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_request_t</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">*</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">if</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">post_action</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">return</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_OK</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">if</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">header_sent</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_log_error</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_LOG_ALERT</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">connection</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">log</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="mi" style="box-sizing: inherit; color: #a980f5;">0</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">,</span> <span class="s" style="box-sizing: inherit; color: #41b7d7;">"header already sent"</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">return</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_ERROR</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">if</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">err_status</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">headers_out</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">.</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">status</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">err_status</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="o" style="box-sizing: inherit; color: #ff8095;">-&gt;</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">headers_out</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">.</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">status_line</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">.</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">len</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="mi" style="box-sizing: inherit; color: #a980f5;">0</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">return</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_top_header_filter</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">r</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">);</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> </pre> </div> </div> <p><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_http_send_response</code> でresponseを生成しています。<br style="box-sizing: inherit;" /><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_http_send_header</code>でヘッダー部分を作ります。<br style="box-sizing: inherit;" />その中で<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_http_top_header_filter</code>を呼び出しています。</p> <p><code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_http_top_header_filter</code>は以下で設定されます。</p> <p><a href="https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L625">https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/ngx_http_header_filter_module.c#L625</a></p> <div class="code-frame" style="box-sizing: inherit; margin: 1.5em -32px; padding: 1em 32px; font-size: 0.9em; position: relative; background-color: #364549; color: #e3e3e3; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" data-lang="c"> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto;"> <pre style="box-sizing: inherit; margin: 0px; padding: 0px; display: block; font-size: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; color: inherit; background-color: transparent; border: none; border-radius: 0px; line-height: 1.8;"><span class="k" style="box-sizing: inherit; color: #ebd247;">static</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_int_t</span> <span class="nf" style="box-sizing: inherit; color: #8bdf4c;">ngx_http_header_filter_init</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">(</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_conf_t</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">*</span><span class="n" style="box-sizing: inherit; color: #e3e3e3;">cf</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">)</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">{</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_top_header_filter</span> <span class="o" style="box-sizing: inherit; color: #ff8095;">=</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">ngx_http_header_filter</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="k" style="box-sizing: inherit; color: #ebd247;">return</span> <span class="n" style="box-sizing: inherit; color: #e3e3e3;">NGX_OK</span><span class="p" style="box-sizing: inherit; color: #e3e3e3;">;</span> <span class="p" style="box-sizing: inherit; color: #e3e3e3;">}</span> </pre> </div> </div> <p>ここで <code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_http_top_header_filter</code> に <code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_http_header_filter</code> を設定しています。<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_http_header_filter</code>は上記の<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">ngx_cpymem</code>で設定してる関数になります。<br style="box-sizing: inherit;" />ここでヘッダーの設定部分と実際にresponseを生成している関数が繋がります。</p> <p> </p> <h3 id="おわりに">おわりに</h3> <p>という事で<code style="box-sizing: inherit; background-color: #eeeeee; color: #333333; padding: 0.1em 0.4em; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;">server_tokens off;</code>の設定から、nginxが実際に設定する所を読んでみました。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%D7%A5%F3%A5%BD%A1%BC%A5%B9">オープンソース</a>は気になったら実際にコードを読み、どんな挙動をしているか明確にわかるのが良いですね。また、その<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>含め、コードから色んな事を学べることが大きな利点だと思います。</p> <p>また機会があればこういう記事を書いていこうと思います。</p> kumamon_engineer 【テレワーク推進!!】OpenTokを使ってビデオチャットを作ってみた hatenablog://entry/26006613627747520 2020-09-16T17:10:27+09:00 2020-09-16T17:10:27+09:00 社内ブログ こんにちは。ばしおです。 まだまだコロナは落ち着きませんね。 昨今のコロナ情勢によりオンラインで打ち合わせをする企業も増えたのはないでしょうか? 企業によってはセキュリティ云々でZoomやWherebyが使えないとかなんとか。 導入できない?じゃ、作るしか無いよね? というわけで今回は OpenTokというサービスを使って、Wherebyみたいなサービスを作りたいと思います。 OpenTokとは? www.vonagebusiness.jp VONAGE(旧Tokbox)社が提供しているWebRTCプラットフォームです。 WebRTCの難しいところをサクッと実装できてしまいます。 … <p>社内ブログ</p> <p>こんにちは。<a href="https://twitter.com/bashi4_">ばしお</a>です。</p> <p>まだまだコロナは落ち着きませんね。<br> 昨今のコロナ情勢によりオンラインで打ち合わせをする企業も増えたのはないでしょうか?<br> 企業によってはセキュリティ云々でZoomやWherebyが使えないとかなんとか。</p> <p>導入できない?じゃ、作るしか無いよね?</p> <p>というわけで今回は<br> OpenTokというサービスを使って、Wherebyみたいなサービスを作りたいと思います。</p> <h3>OpenTokとは?</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.vonagebusiness.jp%2F" title="Vonageのビジネス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.vonagebusiness.jp/">www.vonagebusiness.jp</a></cite></p> <p>VONAGE(旧Tokbox)社が提供しているWebRTCプラットフォームです。 WebRTCの難しいところをサクッと実装できてしまいます。 また、録画(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AB%A5%A4%A5%D6">アーカイブ</a>)することもでき、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>ストレージに保存も可能です。 他にもリアルタイムチャットやブロードキャスト配信も出来るようです。</p> <h3>導入</h3> <p>今回、サーバーサイドではOpenTokが提供しているPHPSDKを使用していきたいと思います。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>は<a href="https://www.slimframework.com/">Slim</a>を使います。(Laravel重たすぎ...)<br> Slimは1ファイルで完結してしまうくらいシンプルな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>です。</p> <p>今回は、一般的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/MVC">MVC</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>に近くなるよう少しカスタマイズしたものを作成したので、これをひな形として使用していきます。</p> <p><a href="https://github.com/bashi4/slim-template.git">https://github.com/bashi4/slim-template.git</a></p> <h3>OpenTokのAPIKey, SecretKey作成</h3> <p>OpenTokに登録していきましょう。<br> 2ページ目で業種を入力するページがありますが、何も入力せずNextでスキップしても大丈夫です。<br> Eメールの認証まで完了すれば登録は完了です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916092348.png" alt="f:id:bashioo:20200916092348p:plain" title="f:id:bashioo:20200916092348p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>ログインをすると、AdminNameを求められます。<br> AdminNameのグループの中に、いくつもプロジェクトを作っていく感じになるので、サービス名などをつけるのがよいでしょう。 今回は「TechBlog」としました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916091427.png" alt="f:id:bashioo:20200916091427p:plain" title="f:id:bashioo:20200916091427p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>次にプロジェクトを作成します。<br> このプロジェクトがAPIKeyの単位となります。こちらは開発やテストなど環境ごとに分けると良いかと思います。今回は「local_video_chat」としました。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916091455.png" alt="f:id:bashioo:20200916091455p:plain" title="f:id:bashioo:20200916091455p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>完了すると<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> KeyとSecret Keyが発行されます。忘れずにメモしておきましょう。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916091645.png" alt="f:id:bashioo:20200916091645p:plain" title="f:id:bashioo:20200916091645p:plain" class="hatena-fotolife" itemprop="image"></span></p> <h3>アプリケーションの実装</h3> <p>雛形をクローンしてきて、トップページを見れる状態にしてください。</p> <p>ReadMe通りにうまく行けば、Hello画面が表示されると思います。</p> <p><a href="https://github.com/bashi4/slim-template#how-to-use">https://github.com/bashi4/slim-template#how-to-use</a></p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ルームの作成</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ルーム作成の実装をしていきましょう。</p> <p>流れとしては、下記になります</p> <ol> <li>トップページの「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ルームを作る」ボタンを押下</li> <li>indexメソッドのpostアクションでルームに対して一意となるセッションIDを作る</li> <li>セッションIDを画面に表示する</li> </ol> <p>ではやっていきましょう。</p> <p>まずは、トップページに「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ルームを作る」ボタンを作ります。</p> <h5>views/index.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code lang-diff" data-lang="diff" data-unlink><span class="synSpecial">- &lt;h1&gt;Hello&lt;/h1&gt;</span> <span class="synIdentifier">+ @if($url)</span> <span class="synIdentifier">+ &lt;a href=&quot;{{ $url }}&quot;&gt;{{ $url }}&lt;/a&gt;</span> <span class="synIdentifier">+ @endif</span> <span class="synIdentifier">+ &lt;form action=&quot;/&quot; method=&quot;post&quot;&gt;</span> <span class="synIdentifier">+ &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;トークルームを作る&lt;/button&gt;</span> <span class="synIdentifier">+ &lt;/form&gt;</span> </pre> <p>get/postでアクションでControllerのindexメソッドを実行するようにrootファイルも修正します。</p> <h5>config/route.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code lang-diff" data-lang="diff" data-unlink><span class="synSpecial">- $app-&gt;get('/', 'App\Controller\Controller:index');</span> <span class="synIdentifier">+ $app-&gt;map(['get', 'post'], '/', 'App\Controller\Controller:index');</span> </pre> <p>次に、OpenTokServiceクラスを作ります。</p> <p>src<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ配下にService<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを作成し、下記の内容でOpenTokService.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a>を作成してください。</p> <h5>src/Service/OpenTokService.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code" data-lang="" data-unlink>&lt;?php namespace App\Service; use OpenTok\OpenTok; use OpenTok\MediaMode; use OpenTok\ArchiveMode; use OpenTok\Role; class OpenTokService { /** @var OpenTok */ protected $opentok; /** @var string */ protected $key; public function __construct() { $this-&gt;key = env(&#39;OPENTOK_KEY&#39;); $this-&gt;opentok = new OpenTok($this-&gt;key, env(&#39;OPENTOK_SECRET&#39;)); } /** * セッションIDを返却 * * @return string */ public function createSession() { return $this-&gt;opentok-&gt;createSession([ // &#39;archiveMode&#39; =&gt; ArchiveMode::ALWAYS, // 常に録画 &#39;mediaMode&#39; =&gt; MediaMode::ROUTED // Routedモード(アーカイブのときはRoutedモード必須) ]); } }</pre> <p>作成したOpenTokServiceクラスをコンテナ経由で呼べるように ServiceProvider修正します。</p> <h5>src/ServiceProvider.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code lang-diff" data-lang="diff" data-unlink>public function register(\Pimple\Container $container) { <span class="synIdentifier">+ $container['videoChatService'] = new \App\Service\OpenTokService();</span> return $container; } </pre> <p>最後に、Controllerのindexメソッドを修正します</p> <h5>src/Controller/Controller.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code lang-diff" data-lang="diff" data-unlink>public function index(Request $request, Response $response) { <span class="synSpecial">- return $this-&gt;app-&gt;view-&gt;render($response, 'index');</span> <span class="synIdentifier">+ $url = null;</span> <span class="synIdentifier">+ if ($request-&gt;isPost()) {</span> <span class="synIdentifier">+ $sessionId = $this-&gt;app-&gt;videoChatService-&gt;createSession();</span> <span class="synIdentifier">+ $url = $request-&gt;getUri()-&gt;getBaseUrl() . &quot;/rooms/{$sessionId}&quot;;</span> <span class="synIdentifier">+ }</span> <span class="synIdentifier">+ return $this-&gt;app-&gt;view-&gt;render($response, 'index', [</span> <span class="synIdentifier">+ 'url' =&gt; $url,</span> <span class="synIdentifier">+ ]);</span> } </pre> <p>実行してみましょう。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916093012.png" alt="f:id:bashioo:20200916093012p:plain" title="f:id:bashioo:20200916093012p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916093026.png" alt="f:id:bashioo:20200916093026p:plain" title="f:id:bashioo:20200916093026p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>セッションIDが作成されURLが表示できました!</p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%C7%A5%AA%A5%C1%A5%E3%A5%C3%A5%C8">ビデオチャット</a>実装する</h3> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ルームのURLの生成まではできました。</p> <p>次はURLから<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ルームに遷移し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%C7%A5%AA%A5%C1%A5%E3%A5%C3%A5%C8">ビデオチャット</a>が開始される部分を作成します。</p> <ol> <li>/rooms/:sessionIdでroomsメソッドを実行し、tokenを発行する</li> <li>tokenを<a class="keyword" href="http://d.hatena.ne.jp/keyword/javascript">javascript</a>のパラメータとして渡し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%C7%A5%AA%A5%C1%A5%E3%A5%C3%A5%C8">ビデオチャット</a>を開始する</li> </ol> <p>では実装していきましょう。</p> <p>rootにアクションを追加します。</p> <h5>config/route.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code lang-diff" data-lang="diff" data-unlink><span class="synIdentifier">+ $app-&gt;get('/rooms/:sessionId', 'App\Controller\Controller:room');</span> </pre> <p>OpenTokServiceクラスに下記を追加します。</p> <h5>src/Service/OpenTokService.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code lang-diff" data-lang="diff" data-unlink>public function getToken($sessionId) { return $this-&gt;opentok-&gt;generateToken($sessionId, [ 'role' =&gt; Role::MODERATOR, // ロールをモデレータに設定 'expireTime' =&gt; time()+(60 * 60), // 有効期限を1時間に設定 ]); } public function getKey() { return $this-&gt;key; } </pre> <p>Controllerにroomメソッドを追加します</p> <h5>src/Controller/Controller.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code" data-lang="" data-unlink>public function room(Request $request, Response $response, $params) { $sessionId = $params[&#39;sessionId&#39;]; $token = $this-&gt;app-&gt;videoChatService-&gt;getToken($sessionId); $key = $this-&gt;app-&gt;videoChatService-&gt;getKey(); $jsonData = [ &#39;sessionId&#39; =&gt; $sessionId, &#39;token&#39; =&gt; $token, &#39;key&#39; =&gt; $key, ]; return $this-&gt;app-&gt;view-&gt;render($response, &#39;room&#39;, [ &#39;params&#39; =&gt; json_encode($jsonData, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT), ]); }</pre> <p>viewsにroom.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a>を作成し下記をコピペしてください。</p> <h5>views/room.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code" data-lang="" data-unlink>&lt;!DOCTYPE html&gt; &lt;html lang=&#34;en&#34;&gt; &lt;head&gt; &lt;meta charset=&#34;UTF-8&#34;&gt; &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt; &lt;meta http-equiv=&#34;X-UA-Compatible&#34; content=&#34;ie=edge&#34;&gt; &lt;title&gt;Document&lt;/title&gt; &lt;link rel=&#34;stylesheet&#34; href=&#34;https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css&#34; integrity=&#34;sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T&#34; crossorigin=&#34;anonymous&#34;&gt; &lt;script src=&#34;https://code.jquery.com/jquery-3.3.1.slim.min.js&#34; integrity=&#34;sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script src=&#34;https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js&#34; integrity=&#34;sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script src=&#34;https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js&#34; integrity=&#34;sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;div class=&#34;container-fluid my-4&#34;&gt; &lt;div class=&#34;row&#34;&gt; &lt;div class=&#34;col-6&#34; style=&#34;height: 80vh;&#34;&gt; &lt;div id=&#34;subscriber&#34;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&#34;col-6&#34; style=&#34;height: 80vh;&#34;&gt; &lt;div id=&#34;publisher&#34;&gt;&lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;!-- ▼ ここから ▼ --&gt; &lt;script src=&#34;https://static.opentok.com/v2/js/opentok.min.js&#34;&gt;&lt;/script&gt; &lt;script&gt; let params = {!! $params !!}; initializeSession(); function handleError(error) { if (error) alert(error.message) } function initializeSession() { const session = OT.initSession(params.key, params.sessionId); const publisher = OT.initPublisher(&#34;publisher&#34;, { targetElement: &#34;publisher&#34;, width: &#34;100%&#34;, height: &#34;100%&#34;, }, handleError); session.on(&#34;streamCreated&#34;, function(event) { session.subscribe(event.stream, &#34;subscriber&#34;, { targetElement: &#34;subscriber&#34;, width: &#34;100%&#34;, height: &#34;100%&#34;, }, handleError); }); session.on(&#34;sessionDisconnected&#34;, function sessionDisconnected(event) { console.error(&#34;You were disconnected from the session.&#34;, event.reason); }); session.connect(params.token, function(error) { error ? handleError(error) : session.publish(publisher, handleError); }); } &lt;/script&gt; &lt;/body&gt; &lt;/html&gt;</pre> <p>これで完了です!</p> <p>先程、生成したURLをクリックしてみましょう</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916100041.png" alt="f:id:bashioo:20200916100041p:plain" title="f:id:bashioo:20200916100041p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>ブラウザで別タブを開き、さらに生成したURLにアクセスしてみましょう</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/b/bashioo/20200916/20200916100107.png" alt="f:id:bashioo:20200916100107p:plain" title="f:id:bashioo:20200916100107p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>1対1の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%C7%A5%AA%A5%C1%A5%E3%A5%C3%A5%C8">ビデオチャット</a>ができました!</p> <h3>リアルタイムチャット</h3> <p>リアルタイムチャットも簡単に実装できます。 下記の内容で room.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a>を上書き、画面をリロードしてみてください。</p> <h5>views/room.blade.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <pre class="code" data-lang="" data-unlink>&lt;!DOCTYPE html&gt; &lt;html lang=&#34;en&#34;&gt; &lt;head&gt; &lt;meta charset=&#34;UTF-8&#34;&gt; &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt; &lt;meta http-equiv=&#34;X-UA-Compatible&#34; content=&#34;ie=edge&#34;&gt; &lt;title&gt;Document&lt;/title&gt; &lt;link rel=&#34;stylesheet&#34; href=&#34;https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css&#34; integrity=&#34;sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T&#34; crossorigin=&#34;anonymous&#34;&gt; &lt;script src=&#34;https://code.jquery.com/jquery-3.3.1.slim.min.js&#34; integrity=&#34;sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script src=&#34;https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js&#34; integrity=&#34;sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;script src=&#34;https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js&#34; integrity=&#34;sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM&#34; crossorigin=&#34;anonymous&#34;&gt;&lt;/script&gt; &lt;style&gt; .message-container { width: 100%; height: 100vh; position: relative; } .message-form { width: 100%; position: absolute; bottom: 15px; } .message-list { height: 90vh; overflow-y: scroll; } .message-list-item { padding: .5rem .75rem; border: 1px solid #ececec; border-radius: .25rem; } .message-list-item.mine { background-color: #17a2b8; color: #fff; margin-left: 4rem; } .message-list-item.theirs { background-color: #f8f9fa; margin-right: 4rem; } &lt;/style&gt; &lt;/head&gt; &lt;body class=&#34;bg-light&#34;&gt; &lt;div class=&#34;container-fluid&#34;&gt; &lt;div class=&#34;row&#34;&gt; &lt;div class=&#34;col-9 d-flex&#34;&gt; &lt;div id=&#34;publisher&#34;&gt;&lt;/div&gt; &lt;div id=&#34;subscriber&#34;&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&#34;col-3 bg-white&#34;&gt; &lt;div class=&#34;message-container&#34;&gt; &lt;div class=&#34;message-list&#34;&gt;&lt;/div&gt; &lt;form class=&#34;message-form&#34;&gt; &lt;div class=&#34;input-group&#34;&gt; &lt;input type=&#34;text&#34; class=&#34;form-control&#34;&gt; &lt;div class=&#34;input-group-append&#34;&gt; &lt;button class=&#34;btn btn-primary&#34; type=&#34;submit&#34;&gt;送信&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/form&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;script src=&#34;https://static.opentok.com/v2/js/opentok.min.js&#34;&gt;&lt;/script&gt; &lt;script&gt; let params = {!! $params !!}; initializeSession(); function handleError(error) { if (error) alert(error.message) } function initializeSession() { const session = OT.initSession(params.key, params.sessionId); // オプションは下記参考 // https://tokbox.com/developer/sdks/js/reference/OT.html#initPublisher const publisher = OT.initPublisher(&#34;publisher&#34;, { insertMode: &#34;append&#34;, width: &#34;360px&#34;, height: &#34;240px&#34;, }, handleError); session.on(&#34;streamCreated&#34;, function(event) { session.subscribe(event.stream, &#34;subscriber&#34;, { insertMode: &#34;append&#34;, width: &#34;360px&#34;, height: &#34;240px&#34;, }, handleError); }); session.on(&#34;sessionDisconnected&#34;, function sessionDisconnected(event) { console.error(&#34;You were disconnected from the session.&#34;, event.reason); }); session.connect(params.token, function(error) { error ? handleError(error) : session.publish(publisher, handleError); }); // ▼ チャット用に追加した部分 ▼ const form = document.querySelector(&#39;.message-form&#39;); const formTxt = document.querySelector(&#39;.message-form input&#39;); form.addEventListener(&#39;submit&#39;, function(event) { event.preventDefault(); session.signal({type: &#39;msg&#39;, data: formTxt.value}, function(error) {}); }); session.on(&#39;signal:msg&#39;, function(event) { var msg = document.createElement(&#39;p&#39;); msg.innerText = event.data; msg.className = event.from.connectionId === session.connection.connectionId ? &#39;message-list-item mine&#39; : &#39;message-list-item theirs&#39;; const messagelist = document.querySelector(&#39;.message-list&#39;); messagelist.appendChild(msg); msg.scrollIntoView(); }); // ▲ チャット用に追加した部分 ▲ } &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </pre> <h3>まとめ</h3> <p>いかがだったでしょうか? Opentokを使って、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%C7%A5%AA%A5%C1%A5%E3%A5%C3%A5%C8">ビデオチャット</a>が簡単に実装できました。<br> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%C7%A5%AA%A5%C1%A5%E3%A5%C3%A5%C8">ビデオチャット</a>を使ったサービスの開発に対し、かなり技術的ハードルが下がったんじゃないでしょうか?</p> <p>最後に、今回動作確認で使った<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>を置いときます。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fbashi4%2Fopentok-video-chat" title="bashi4/opentok-video-chat" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/bashi4/opentok-video-chat">github.com</a></cite></p> bashioo 【話題沸騰中】軽量でお洒落なコマンドプロンプト拡張ライブラリ「Starship」を導入した話 hatenablog://entry/26006613619567067 2020-08-28T17:02:20+09:00 2020-08-28T22:56:10+09:00 Hajimariのエンジニアが【話題沸騰中】軽量でお洒落なプロンプト「Starship」を導入した話になります。 <p>こんにちは。 分からないことに出会ったら、ワクワクする inteeの <a href="https://twitter.com/channaka0531">中野</a> です。</p> <p>会社から新型<a class="keyword" href="http://d.hatena.ne.jp/keyword/MacBook%20Pro">MacBook Pro</a>を支給して貰ったので、 ターミナルのプロンプトをカスタマイズした話をしようと思います!</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/Starship">Starship</a>とは</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/channaka0531/20200825/20200825141702.png" alt="f:id:channaka0531:20200825141702p:plain" title="f:id:channaka0531:20200825141702p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Starship">Starship</a>とは、ターミナルのプロンプトをカスタマイズする<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3">プラグイン</a>です。 日本語翻訳が充実しており、可愛いデザインに惚れて、導入を決めました!</p> <h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/Starship">Starship</a>の特徴</h5> <ul> <li>Rust言語で開発されており、動作が高速</li> <li>設定ファイルで細かくカスタマイズ可能</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/bash">bash</a> / <a class="keyword" href="http://d.hatena.ne.jp/keyword/Zsh">Zsh</a> / Fish で使用可能</li> <li>Git管理のプロジェクトであれば、ブランチ名や言語バージョン等が表示可能</li> <li>ポップなカラースキーム・絵文字がカワイイ(超重要)</li> </ul> <p>個人的に辛い開発時にエモい気持ちになりながら、開発できる体験を重要視しております🙇‍♀️</p> <p><a href="https://raw.githubusercontent.com/starship/starship/master/media/demo.gif" class="http-image"><img src="https://raw.githubusercontent.com/starship/starship/master/media/demo.gif" class="http-image" alt="https://raw.githubusercontent.com/starship/starship/master/media/demo.gif"></a></p> <h2>インストール</h2> <p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Mac">Mac</a>で導入した為、Homebrew(パッケージマネージャー)経由でインストールしました。 他の導入の仕方に関しては、<a href="https://starship.rs/ja-JP/">日本語翻訳された公式サイト</a> を見た方が良いです!</p> <pre class="code" data-lang="" data-unlink>brew install starship</pre> <p>次に初期化のための<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>をシェルの設定ファイルに追加します。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Bash">Bash</a>の場合、 ~/.bashrc に</p> <pre class="code" data-lang="" data-unlink># ~/.bashrc eval &#34;$(starship init bash)&#34;</pre> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Zsh">Zsh</a>の場合、 ~/.zshrc に</p> <pre class="code" data-lang="" data-unlink># ~/.zshrc eval &#34;$(starship init zsh)&#34;</pre> <p>Fishの場合、 ~/.config/fish/config.fish に</p> <pre class="code" data-lang="" data-unlink># ~/.config/fish/config.fish starship init fish | source</pre> <p>上記の設定を反映後、ターミナルが下記画像になっていたら、インストール完了です!</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/channaka0531/20200825/20200825112852.png" alt="f:id:channaka0531:20200825112852p:plain" title="f:id:channaka0531:20200825112852p:plain" class="hatena-fotolife" itemprop="image"></span></p> <h2>カスタマイズ</h2> <p>各項目の表示を設定したいので、~/.config/<a class="keyword" href="http://d.hatena.ne.jp/keyword/starship">starship</a>.toml ファイルを作成します。</p> <pre class="code" data-lang="" data-unlink>$ touch ~/.config/starship.toml</pre> <p>各項目の設定内容については、 <a href="https://starship.rs/config/">日本語翻訳された公式サイト</a> を確認するのが手っ取り早いです💪</p> <h2>個人設定</h2> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/c/channaka0531/20200825/20200825140624.png" alt="f:id:channaka0531:20200825140624p:plain" title="f:id:channaka0531:20200825140624p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>まだまだカスタマイズ途中ではありますが、設定ファイルの内容をアップロードしておきます! 皆さんも<a class="keyword" href="http://d.hatena.ne.jp/keyword/Starship">Starship</a>を導入して、<b>カワイイ開発体験</b>を試してみてください^^</p> channaka0531 自然言語処理を使って弊社エンジニアメンバーの特徴を言語化してみた hatenablog://entry/26006613612754046 2020-08-12T15:49:39+09:00 2020-08-13T13:17:24+09:00 こんにちは! Hajimariの新卒2年目の栗岡です! 普段は受託開発を行いながら、採用担当をしています! 開発と採用の2足草鞋は内定者インターンから含めるとかれこれ2年になります。 今回は、プログラミングの力を使って、エンジニア採用でちょっと課題と感じていることを少しでも解消してみようと思います! (結論、そんなに簡単じゃなかったです、、、) 感じている課題 弊社のエンジニア組織/メンバーの特徴を言語化できていない故に、3年後・5年後にエンジニア組織の強みが分からなくなりそう、、、、 今は、会社自体が若く、小規模であるため、業務の幅の広さや裁量権の大きさを打ち出せるけれど、もし数年後従業員数… <p>こんにちは! Hajimariの新卒2年目の<a href="https://twitter.com/maroooonLaravel">栗岡</a>です!</p> <p>普段は受託開発を行いながら、採用担当をしています! 開発と採用の2足草鞋は内定者<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>から含めるとかれこれ2年になります。</p> <p>今回は、プログラミングの力を使って、エンジニア採用でちょっと課題と感じていることを少しでも解消してみようと思います! <s>(結論、そんなに簡単じゃなかったです、、、) </s></p> <h4>感じている課題</h4> <p><b> 弊社のエンジニア組織/メンバーの特徴を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%B2%BD">言語化</a>できていない故に、3年後・5年後にエンジニア組織の強みが分からなくなりそう、、、、 </b></p> <p>今は、会社自体が若く、小規模であるため、業務の幅の広さや<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%DB%CE%CC%B8%A2">裁量権</a>の大きさを打ち出せるけれど、もし数年後従業員数が100人を超えたときに、どんな風に候補者さんに弊社のエンジニア組織を魅力的に思ってもらえる のか結構悩んでいます。</p> <h4>やること</h4> <p>組織は人の集まりなので、とりあえず、エンジニア組織のメンバーの特徴を改めて理解し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B8%C0%B8%EC%B2%BD">言語化</a>したいと思います。 ざっくり言うと、「弊社のエンジニア組織の特徴としては、〜な人と・・・な人にカテゴライズできて〜」ってことを言えるようになりたい。</p> <h4>具体的な方法</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/wantedly">wantedly</a>の入社ブログから、それぞれメンバーの特徴を抽出・分類する。 ちゃんと考えて書いた言葉は、人の特徴を表すので、とりあえずやってみるにはちょうど良いかなと思いました。</p> <p>実装方法としては、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%C1%B3%B8%C0%B8%EC%BD%E8%CD%FD">自然言語処理</a>でおなじみtf-idfでブログからメンバーの特徴を抽出しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.sejuku.net%2Fblog%2F26420" title="自然言語処理の基礎技術!tf-idfを簡単に解説! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.sejuku.net/blog/26420">www.sejuku.net</a></cite></p> <p>いろんな人が書いた文章を比較した際に、その人っぽい単語・言葉を見つけるって感じですかね。</p> <p><span style="font-size: 80%"><span style="color: #d32f2f">*<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%C1%B3%B8%C0%B8%EC%BD%E8%CD%FD">自然言語処理</a>や統計分析は大学の頃にほんの少しだけ触った程度ですので、初心者中の初心者です</span> </span></p> <h4>やってみた</h4> <p>普段データ分析で遊ぶときは、jupyter notebookを使うのですが、今回、色々と探していると、Streamlitと言うデータ分析の結果を凄くwebっぽく表示してくれる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a> があったので使ってみました。 なので、言語も<a class="keyword" href="http://d.hatena.ne.jp/keyword/python">python</a>です。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.streamlit.io%2F" title="Streamlit — The fastest way to create data apps" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.streamlit.io/">www.streamlit.io</a></cite></p> <h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%C1%C2%D6%C1%C7%B2%F2%C0%CF">形態素解析</a> ブログの内容を単語ごとに区切る</h5> <p>前段階として、アナログに<a class="keyword" href="http://d.hatena.ne.jp/keyword/wantedly">wantedly</a>のブログをコピペし、メンバーごとのテキストファイルを作りました。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synComment"># nameにはメンバーの名前がloopで入ります</span>  <span class="synStatement">def</span> <span class="synIdentifier">sprit_sentence_to_word</span>(name):   <span class="synComment"># 辞書登録(mecab-ipadic-neologd</span>   option = <span class="synConstant">&quot;-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd&quot;</span>   tagger = MeCab.Tagger(<span class="synConstant">&quot;-Ochasen &quot;</span> + option)   <span class="synComment"># アナログに作ったテキストデータリンク</span>   open_link = <span class="synConstant">&quot;/text_data/wantedly/&quot;</span> + name + <span class="synConstant">&quot;.txt&quot;</span>   f = <span class="synIdentifier">open</span>(open_link)   text = f.read()   f.close()   <span class="synComment"># パースする </span>   res = tagger.parseToNode(text)   <span class="synComment"># ストップワードリスト取得</span>   path = <span class="synConstant">&quot;stop_words.txt&quot;</span>   <span class="synComment"># 日本語のストップワードを取得し、リスト化します</span>   stop_words = create_stopwords(path)   words = []   <span class="synStatement">while</span> res: <span class="synComment"># ストップワード除去</span>  <span class="synStatement">if</span> res.surface <span class="synStatement">not</span> <span class="synStatement">in</span> stop_words:  word = res.surface  part_of_speech = res.feature.split(<span class="synConstant">&quot;,&quot;</span>)[<span class="synConstant">0</span>]  sub_part_of_speech = res.feature.split(<span class="synConstant">&quot;,&quot;</span>)[<span class="synConstant">1</span>]  <span class="synStatement">if</span> part_of_speech <span class="synStatement">in</span> [<span class="synConstant">'名詞'</span>, <span class="synConstant">'動詞'</span>, <span class="synConstant">'形容詞'</span>]:  <span class="synStatement">if</span> sub_part_of_speech <span class="synStatement">not</span> <span class="synStatement">in</span> [<span class="synConstant">'空白'</span>, <span class="synConstant">'*'</span>]:  words.append(word)   res = res.next  <span class="synStatement">return</span> words </pre> <h5><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%C1%C2%D6%C1%C7%B2%F2%C0%CF">形態素解析</a>した結果をファイル保存</h5> <pre class="code lang-python" data-lang="python" data-unlink>  data = sprit_sentence_to_word(name) open_link = <span class="synConstant">&quot;/output/wantedly/&quot;</span> + name + <span class="synConstant">&quot;.txt&quot;</span>  <span class="synStatement">with</span> <span class="synIdentifier">open</span>(open_link, <span class="synConstant">'w'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: f.write(<span class="synConstant">' '</span>.join(data))  <span class="synComment"># こんな感じ単語区切りになり保存されます</span>  石川県 生まれ 札幌 住み 現在 千葉県 住ん 5歳 高校 卒業 サッカー 漬け 日々 大好き サッカー 辞め  休日 <span class="synConstant">1</span> ミリ 出 多い ん インドア ん 笑 大学 青山学院大学 進学 </pre> <h5>TF-IDFの計算</h5> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synStatement">def</span> <span class="synIdentifier">analysis</span>(name):   contents = []   eng_names = []   open_link = <span class="synConstant">&quot;/output/wantedly/&quot;</span> + name + <span class="synConstant">&quot;.txt&quot;</span>      <span class="synComment"># ファイルをオープンする</span>   test_data = <span class="synIdentifier">open</span>(open_link, <span class="synConstant">&quot;r&quot;</span>)   <span class="synComment"># データを書き込む</span>   contents.append(test_data.read())   <span class="synComment"># ファイルクローズ</span>   test_data.close()   <span class="synComment"># エンジニアの名前も詰める</span>   eng_names.append(name)   <span class="synComment"># データフレームにする</span>   df = pd.DataFrame({<span class="synConstant">'name'</span>: eng_names, <span class="synConstant">'text'</span>: contents})      <span class="synComment"># TF-IDFの計算</span>   <span class="synComment"># TF-IDFの上位200単語・単語頻出度10%~90%を取得</span>   tfidf_vectorizer = TfidfVectorizer(use_idf=<span class="synIdentifier">True</span>, min_df=<span class="synConstant">0.1</span>, max_df=<span class="synConstant">0.90</span>, max_features=<span class="synConstant">200</span>)     <span class="synComment"># Tfidf値を取得</span>   tfidf_matrix = tfidf_vectorizer.fit_transform(df[<span class="synConstant">'text'</span>])      <span class="synComment"># index 順の単語リスト</span>   terms = tfidf_vectorizer.get_feature_names()   tfidfs = tfidf_matrix.toarray()   <span class="synComment"># TF-IDF表示</span>   show_tfidf(terms, tfidfs, eng_names)    <span class="synStatement">def</span> <span class="synIdentifier">show_tfidf</span>(terms, tfidfs, eng_names):   df = pd.DataFrame(     tfidfs,     columns=terms,     index=eng_names)  <span class="synComment">#表で出力</span>  st.write(df) </pre> <p>こんな感じに出力されました。 数値が1に近いほど特徴的な単語になります。200単語あるので全てお見せすることは難しいですが、、 <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/marron-web-engineer/20200811/20200811091345.png" alt="f:id:marron-web-engineer:20200811091345p:plain" title="f:id:marron-web-engineer:20200811091345p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>ちょっと考察ができそうな単語をいくつかみてみます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/marron-web-engineer/20200811/20200811234346.png" alt="f:id:marron-web-engineer:20200811234346p:plain" title="f:id:marron-web-engineer:20200811234346p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>事業という単語からは、エンジニアとして事業を作っていきたいと普段から発信している<a href="https://twitter.com/channaka0531">中野</a>が高い数値となっています。</p> <p>仲間という単語では、CTOの柳澤と新卒1年目の<a href="https://twitter.com/michael_progs">保坂</a>に高い数値が出てます。 仲間(一緒に働く人)が軸に入っている候補者の方との面談に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B5%A5%A4">アサイ</a>ンしたいです。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/marron-web-engineer/20200811/20200811234401.png" alt="f:id:marron-web-engineer:20200811234401p:plain" title="f:id:marron-web-engineer:20200811234401p:plain" class="hatena-fotolife" itemprop="image"></span> 成長・技術という単語では、<a href="https://twitter.com/bashi4_">板橋</a>に高い数値が出ています。 ブログを読んでもらってから面接を行い、入社からの成長を技術周りの事を交えて話してもらえると候補者体験として効果的かもしれません。</p> <p>それぞれの単語をみていくだけでもたくさんの考察や気づきがあるのですが、やりたいことはエンジニ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%F3%A5%D0%A1%BC">アメンバー</a>を近い特徴同士でグルーピングすることなので、次元圧縮を行いメンバーそれぞれの特徴をまとめて数値にします。 今回は、次元圧縮をちょっと話題になっているらしいのでt-SNEを使ってみます。 詳しくはこちらをご覧ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fstfate%2Fitems%2F8988d01aad9596f9d586" title="t-SNEによるイケてる次元圧縮&amp;可視化 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/stfate/items/8988d01aad9596f9d586">qiita.com</a></cite></p> <h5>次元圧縮</h5> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synComment"># tsne次元圧縮 </span>  <span class="synComment"># tfidf_matrix:上部で表示したtfidfデータ</span>  <span class="synComment"># df.name:エンジニアメンバー名前データ</span>  do_tsne(tfidf_matrix, df.name)  <span class="synStatement">def</span> <span class="synIdentifier">do_tsne</span>(tfidf_matrix, name):   <span class="synComment"># 2次元に変換 回転数1200</span>   tsne = TSNE(n_components=<span class="synConstant">2</span>, perplexity=<span class="synConstant">50</span>, n_iter=<span class="synConstant">1200</span>)   tsne_tfidf = tsne.fit_transform(tfidf_matrix)   <span class="synComment"># データフレームにセット</span>   df_tsne = pd.DataFrame(tsne.embedding_[:, <span class="synConstant">0</span>],columns = [<span class="synConstant">&quot;x&quot;</span>])   df_tsne[<span class="synConstant">&quot;y&quot;</span>] = pd.DataFrame(tsne.embedding_[:, <span class="synConstant">1</span>])   <span class="synComment"># プロットする際に名前で表示させる</span>   df_tsne[<span class="synConstant">&quot;name&quot;</span>] = df.name   <span class="synComment"># テーブルで表示 </span>   st.write(df_tsne)   <span class="synComment"># プロットグラフで表示</span>   c = alt.Chart(df_tsne).mark_point().encode(     x=<span class="synConstant">'x'</span>,     y=<span class="synConstant">'y'</span>,    ).mark_text(     align=<span class="synConstant">'left'</span>,     baseline=<span class="synConstant">'middle'</span>,     dx=<span class="synConstant">10</span>    ).encode(     text=<span class="synConstant">'name'</span>,    )   st.altair_chart(c, use_container_width=<span class="synIdentifier">True</span>) </pre> <h4>結果</h4> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/marron-web-engineer/20200812/20200812002537.png" alt="f:id:marron-web-engineer:20200812002537p:plain" title="f:id:marron-web-engineer:20200812002537p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><b>微妙。。。</b>散らばっているので、特徴ごとにグルーピングもできず、、、、 一箇所くらいまとまりあれば面白かったのですが、、、、 そんなに簡単にはいかないそうです。リベンジします。</p> <p><b>最後に</b>、ちょっと流行ってるword2vecで〜に〜を聞いてみたシリーズに乗っかり、</p> <p><span style="font-size: 80%">*word2vecとは、文章中の単語や単語間の意味を数値化(ベクトル化)する、<a class="keyword" href="http://d.hatena.ne.jp/keyword/google">google</a>の偉い方が考えた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CB%A5%E5%A1%BC%A5%E9%A5%EB%A5%CD%A5%C3%A5%C8%A5%EF%A1%BC%A5%AF">ニューラルネットワーク</a>モデルです。 </span></p> <p>Hajimariのエンジニア組織にビジョンである<b>「自立」</b>を聞いてみました。 tf-idf同様<a class="keyword" href="http://d.hatena.ne.jp/keyword/wantedly">wantedly</a>の入社ブログが元データです。</p> <p>参考にさせて頂きました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnote.com%2Fshimakaze_soft%2Fn%2Fnaec4ac9dcb04" title="word2vecを用いて久保建英選手の評価を分析してみる|shimakaze_soft|note" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://note.com/shimakaze_soft/n/naec4ac9dcb04">note.com</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>のメインは以下の通り</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synStatement">def</span> <span class="synIdentifier">filler_word2vec</span>():   <span class="synComment"># sample.txtには、ブログデータが単語区切りで入っています。</span>   sentences = word2vec.LineSentence(<span class="synConstant">'/output/sample.txt'</span>)   <span class="synComment"># ブログデータベクトル化</span>   <span class="synComment"># 出現5単語未満切り捨て</span>   model = word2vec.Word2Vec(    sentences,    sg=<span class="synConstant">1</span>,    size=<span class="synConstant">100</span>    min_count=<span class="synConstant">5</span>,    window=<span class="synConstant">8</span>,    hs=<span class="synConstant">1</span>,    <span class="synIdentifier">iter</span>=<span class="synConstant">5</span>   )      <span class="synComment">#学習モデル作成</span>   model.save(<span class="synConstant">'/model/sample.model'</span>)    <span class="synStatement">def</span> <span class="synIdentifier">analysis</span>():   load_model_path = <span class="synConstant">'/model/sample.model'</span>   <span class="synComment">#学習モデル読み込み</span>   model = word2vec.Word2Vec.load(load_model_path)      <span class="synComment">#自立に近い意味合いの単語を出力</span>   results = model.wv.most_similar(positive=[<span class="synConstant">'自立'</span>])   <span class="synStatement">return</span> results    model = filler_word2vec()  results = analysis() </pre> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/marron-web-engineer/20200812/20200812011225.png" alt="f:id:marron-web-engineer:20200812011225p:plain" title="f:id:marron-web-engineer:20200812011225p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p><span style="font-size: 80%">*1に近いほど類似しています </span></p> <p>データ量が少ない関係で、数値が固まってしまいましたが、</p> <p><b>「夢のために、新しいことに挑戦し、働く」 </b></p> <p>といった弊社らしい「自立」を解釈できるアウトプットになりました。 (少し無理やり感もありますが、、笑)</p> <h4>まとめ</h4> <ul> <li><p>元となるデータが<a class="keyword" href="http://d.hatena.ne.jp/keyword/wantedly">wantedly</a>でよかったのか?</p></li> <li><p>データ数少なすぎじゃないか?</p></li> <li><p>分析の方法が本当に適切なのか?</p></li> </ul> <p>などなど突っ込みどころ&amp;反省もある内容であったと思いますが、採用という定性的で感覚が大きいテーマにおいて、言葉を数値化することで分かることも一定ありそうだなと思いました。 ちょっと大学で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%C1%B3%B8%C0%B8%EC">自然言語</a>っぽいこと齧った程度では歯が立たないことは実感させられましたが、せっかくエンジニアと他の職務も兼務できる環境なので、プログラミングをいろんな分野に使っていけたらなと思います!</p> <p><b>夢のために、新しいことに挑戦し、働く </b> エンジニア組織ですが、まだまだ仲間がたりません!事業や組織作りに興味がある・モチベーション高い仲間と働きたいエンジニアさんがいらっしゃいましたら、是非お話しましょう!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F253705" title="21卒!新卒4期生として会社を創っていきたい新卒エンジニアを募集! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/253705">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" title="リードエンジニアを募集!急成長スタートアップ企業で自社サービスを作る!! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> <p><iframe id="talk_frame_633896" src="//speakerdeck.com/player/e6b2a24e1d454e1ebafc5ace0f57e4db" width="710" height="501" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/hajimari/company-information-materials-of-hajimari-inc">speakerdeck.com</a></cite></p> marron-web-engineer 【Laravel】画像の直アクセスを禁止して、特定の条件を突破した場合のみ画像を表示する方法 hatenablog://entry/26006613605447589 2020-07-30T16:26:26+09:00 2020-07-30T16:26:26+09:00 こんにちは!Hajimariの新卒エンジニアの稲葉です。 2020年4月1日に新卒エンジニア2期生として入社しました! 普段は、自社プロダクトであるスタートアップ向けマッチングサイト構築パッケージPIECE(https://crowd.itpropartners.com/piece/)の開発や受託開発を行っています! 今回はログインしていない場合に画像の直アクセスを禁止する方法について書いていきます。 htaccessで直アクセスを制限する方法も考えられますが、ログインしている場合は直アクセスの許可するため、Laravel、Nginxで実装していきます。 直アクセス storage/files… <p>こんにちは!Hajimariの新卒エンジニアの稲葉です。<br /> 2020年4月1日に新卒エンジニア2期生として入社しました!</p> <p>普段は、自社プロダクトであるスタートアップ向けマッチングサイト構築パッケージPIECE(<a href="https://crowd.itpropartners.com/piece/">https://crowd.itpropartners.com/piece/</a>)の開発や受託開発を行っています!</p> <p>今回はログインしていない場合に画像の直アクセスを禁止する方法について書いていきます。<br /> htaccessで直アクセスを制限する方法も考えられますが、ログインしている場合は直アクセスの許可するため、Laravel、Nginxで実装していきます。</p> <h3>直アクセス</h3> <p>storage/files配下に設置した画像が、 <code>/storage/files/画像名</code>でアクセスした際に画像が表示されます。<br /> 特定の条件を突破した場合のみ表示させたい画像がある場合は、直アクセスを禁止してみてください。<br /> 万が一直アクセスをされた際に、画像が表示されるの防ぐことができます。</p> <h3>実装</h3> <p>まずはstorage配下にimages<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを作成。<br /> 今回直リンクを禁止する画像をstorage/images配下に設置します。</p> <p>画像の設置後、Nginxでlocationの設定をします。 /imagesにアクセスした際、内部リダイレクトされます。</p> <pre class="code" data-lang="" data-unlink>location /images { try_files /index.php?$uri /index.php?$query_string; }</pre> <p><br> 次にルーティングの定義です。<br /> whereメソッドはパラメーターのフォーマットを制約します。<br /> 下記ではパラメーター(path)を0文字以上の任意の文字列に制約。</p> <pre class="code" data-lang="" data-unlink>Route::get(&#39;/images/{path?}&#39;, &#39;FileAccessController@index&#39;)-&gt;where([&#39;path&#39; =&gt; &#39;.*&#39;]);</pre> <p><br> ルーティングの定義後、traitの作成です。<br /> traitの作成後は必要なメソッドを定義していきます。<br /> 作成したtraitは任意のクラスで継承すれば、継承したtraitに定義されているメソッドをそのクラスが利用することができます。</p> <pre class="code" data-lang="" data-unlink>use Illuminate\Support\Facades\Storage; Trait ImageTrait {   public function getDiskInstance() { return Storage::disk(&#39;local&#39;); }   public function storageExist($fileName) { return $this-&gt;getDiskInstance()-&gt;exists($fileName); } }</pre> <p>getDiskInstance()メソッドでは、Storage<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%A1%A5%B5%A1%BC%A5%C9">ファサード</a>のdiskメソッドを使用してディスク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>の取得。<br /> storageExist()メソッドでは、existsメソッドを使用してファイルが存在しているか判定します。<br /> 存在する場合はtrueを返します。</p> <p><br> trait作成後、filesystems.<a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a> で local のベース<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リがどこになるかを指定します。<br /> 今回はstorage/images配下に画像を設置したため、images<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを指定。<br /> デフォルトではapp<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リが指定されています。</p> <pre class="code" data-lang="" data-unlink>&#39;disks&#39; =&gt; [ &#39;local&#39; =&gt; [ &#39;driver&#39; =&gt; &#39;local&#39;, // 変更前 // &#39;root&#39; =&gt; storage_path(&#39;app&#39;), &#39;root&#39; =&gt; storage_path(&#39;images&#39;), ],</pre> <p><br> 最後にコントローラの作成、indexメソッドを定義をします。<br /> 先ほど作成したtraitを下記で継承することが可能です。</p> <pre class="code" data-lang="" data-unlink>use トレイト名;</pre> <p>ログインしている場合のみ直リンクを許可します。</p> <pre class="code" data-lang="" data-unlink>class FileAccessController extends Controller { use ImageTrait; public function index($path=null, Request $request) { $exist = $this-&gt;storageExist($path); if (!$exist) { abort(404); } if(Auth::check()){ return $this-&gt;getDiskInstance()-&gt;response($path); }else { abort(404); } } }</pre> <p>実装は以上です。</p> <p><br> 現在は家族の事情もあり、リモートワークを行っています。<br /> 内定者<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A1%BC%A5%F3">インターン</a>から数えると約10ヶ月リモートワークを継続中です。<br /> 今後は長期間のリモートワークについても書いていこうと思います。</p> <p><br></p> <h3>最後に</h3> <p>Hajimariでは、Laravel、vue.js、Nuxt.jsで開発に挑戦したいエンジニア・デザイナーを絶賛募集中です!</p> <p>そして、22卒新卒エンジニアのエントリーも心よりお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F83872" title="<1人目UI/UXデザイナー求む>デザインに関わる全ての裁量権を任せます! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/83872">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" title="リードエンジニアを募集!急成長スタートアップ企業で自社サービスを作る!! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> pmo23 GAS と Slack App でデータ集計用のボットを作りました hatenablog://entry/26006613595903587 2020-07-15T09:15:54+09:00 2020-07-16T12:03:52+09:00 こんにちは!株式会社Hajimariの新卒エンジニアの濵田です。 今年の4月に新卒として入社致しました。 普段はGraspyの開発業務を行う傍ら 最近は会社のHPの管理を行ったりしています。 今回はGASとSlack Appで作った「360度レビューくん」がどう動いているのかを紹介します。 ※このbotはエンジニアチームの中で走っている施策「360˚ソースコードレビュー」で使うために作成しました。 上記の施策に関しては責任者をされているエンジニアの先輩が解説してくださったこちらの記事でかなり詳しく解説されているのでぜひご一読ください。 bot の概要 slack の特定のチャンネルに配置してお… <p>こんにちは!<a href="https://www.hajimari.inc/">株式会社Hajimari</a>の新卒エンジニアの濵田です。</p> <p>今年の4月に新卒として入社致しました。 普段は<a href="https://graspy.jp/">Graspy</a>の開発業務を行う傍ら</p> <p>最近は会社のHPの管理を行ったりしています。 今回はGASとSlack Appで作った「360度レビューくん」がどう動いているのかを紹介します。</p> <p>※この<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>はエンジニアチームの中で走っている施策「360˚<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>レビュー」で使うために作成しました。 上記の施策に関しては責任者をされているエンジニアの先輩が解説してくださった<a href="https://note.com/hajimari_0226/n/n25aa57671f63">こちらの記事</a>でかなり詳しく解説されているのでぜひご一読ください。</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a> の概要</h2> <p>slack の特定のチャンネルに配置しておき、メンションをつけて<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>のプルリクのURLを送ると プルリクについた会話(conversation)を数えてシートに記録します。 また、メンションつけて「データをみたい」とメッセージを送信すると集計された内容が チャンネルに投稿されます。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200715/20200715092206.jpg" alt="f:id:hajimari_hamada:20200715092206j:plain" title="f:id:hajimari_hamada:20200715092206j:plain" class="hatena-fotolife" itemprop="image"></span> 上記の写真ではプルリクのURLを<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>にメンションつけて送る事で集計してもらっているシーンです。</p> <p>今回はこの<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>がどのようにして集計して、シートに記録・チャンネルに返信しているのか、ついて書いていきます。</p> <h2>全体的な<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>の構成</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Bot">Bot</a>が動く流れとしては以下の通りです。</p> <p>① Slack で<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>にメンションつけてイベント発生 => GASの<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>叩く ② GAS側では届いたURLを元に<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を叩いて会話(今後コメントと表記)を取得。処理してシートに書き出し、レスポンスを返す。 ③ 戻ってきたレスポンスにあるメッセージを<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>がチャンネルに投稿する</p> <p>こちらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>はGASプログラム(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>)、Slack App(<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>)を使って動いています。 それぞれ具体的にどのようになっているのか見ていきます。</p> <h4>GAS (<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>)</h4> <p>GASのプログラムは主に以下のような項目のコードで動いています。</p> <blockquote><ol> <li>slack との最初の連携をする部分</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を叩く</li> <li>計算処理</li> <li>シートに書き込む</li> <li>Slack チャンネルにメッセージを送信</li> </ol> </blockquote> <p>以下で順番に見ていきます。</p> <h6>1. slack との最初の連携をする部分</h6> <p><figure class="figure-image figure-image-fotolife" title="GAS に来たデータを取得する部分です"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714155903.png" alt="f:id:hajimari_hamada:20200714155903p:plain" title="f:id:hajimari_hamada:20200714155903p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>GAS に来たデータを取得する部分です</figcaption></figure></p> <p>上記写真の①では、このGASのプログラムに来たデータを取得しています。 後述するSlack 側の設定をしておくと、postData.event.text でメンションつけて送信したメッセージの内容が来ています。 今回は別箇所にプルリクURLだけ切り出す関数を用意してURLのみ使っていますが、 受け取ったメッセージはお好きなようにご利用ください。</p> <p>そして②では、Slack側から来たデータが、認証用のリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トだった場合に返してあげる内容を書いています。 このコードでSlack 側の期待したレスポンスが返ってくるかのチェックを通ることができます。 <a href="https://tech-cci.io/archives/2683">この記事</a>を参考に(<s>というかそのまま</s>)用意しました。</p> <p>Slack App の設定の部分で改めて説明します。</p> <h6>2. <a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を叩く</h6> <p><figure class="figure-image figure-image-fotolife" title="GitHub の API を叩いてレスポンスを返すメソッドです"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714155214.png" alt="f:id:hajimari_hamada:20200714155214p:plain" title="f:id:hajimari_hamada:20200714155214p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption><a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> の <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を叩いてレスポンスを返すメソッドです</figcaption></figure></p> <p>このメソッドは<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩くメソッドで、書いてる通りではあるのですが、 引数に来た <a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> の url を叩いてレスポンスを返してくれるメソッドです。</p> <p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>のプルリクについた会話を集計したいので、 引数に入ってくる apiUrl には</p> <blockquote><p><span data-unlink>https://api.github.com/repos/ { レポジトリ名 }/pulls/{ プルリクの数値(#XXXXみたいに表示されてるやつ) }/comments</span> が入ってきます。</p></blockquote> <p>また、今回は会社のレポジトリのプルリク情報を取得しているので<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンをheaders に指定しています。</p> <blockquote><p>PropertiesService.getScriptProperties().getProperty('<a class="keyword" href="http://d.hatena.ne.jp/keyword/GITHUB">GITHUB</a>_TOKEN')</p></blockquote> <p>上記に関して少し触れておくと、GASには今回のような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンなど、ベタ書きは避けたい情報を設定する時 別に置いておける場所があり、それをプロパティストアといいます。</p> <p>そしてそこに置いておいた情報にアクセスしたりするGASの機能の事をプロパティサービス(ProperiiesService)といい、 上記のコードでは、それを使って予め保管しておいた<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A1%BC%A5%AF">トーク</a>ンを呼び出しています。</p> <p>今回は飛んできたプルリクURLをこのメソッドで使えるAPIURLに変換する部分の解説は割愛します。</p> <h6>3. 計算処理</h6> <p><figure class="figure-image figure-image-fotolife" title="そのプルリクにどのメンバーがどれだけコメントしたかを計算するメソッド"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714162009.png" alt="f:id:hajimari_hamada:20200714162009p:plain" title="f:id:hajimari_hamada:20200714162009p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>そのプルリクにどのメンバーがどれだけコメントしたかを計算するメソッド</figcaption></figure></p> <p>上記のメソッドは、そのプルリクについたコメント数をカウントするいくつかのメソッドの中の一つです。 ここではコメントの通り、そのプルリクについたコメントを自分以外の誰が何回したかを算出しています。</p> <p>引数について解説すると、このGASのプログラムではそもそもエンジニアチームメンバー全員の <a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の名前とSlack のメンバーID を50音順で配列にして管理しています。 ここでは<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>の名前の配列を受け取っています。</p> <p>また reviews は上記の<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> を叩いた戻りを受け取っています。 reviews にはそのプルリクについた全てのコメントが入っており、それぞれのコメントオブジェクトのuser.loginに入っています。 そして第3引数のuserGithubNameには、このプルリクをチャンネルで共有したメンバーの<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> 上の名前を受け取っています。</p> <p>上記のメソッドはそのプルリクに自分以外の人がどれだけコメントしているのかを集計しようとしていますが これらの<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a> の名前を使ってそれを実現しています。</p> <p>ちなみに reviewCounts は配列で(名前どうにかするべきかも)、後々シートにデータを入れる際の事を考慮して用意しています。 それ専用のメソッドを読んで先にメンバー分の中身が入った配列を用意しています。</p> <h6>4. シートに書き込む</h6> <p><figure class="figure-image figure-image-fotolife" title="今回書き込むシートを取得しています"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714164605.png" alt="f:id:hajimari_hamada:20200714164605p:plain" title="f:id:hajimari_hamada:20200714164605p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>今回書き込むシートを取得しています</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="上記のメソッドを使って他のメンバーからもらったコメント数を使ってシートに書いています"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714164408.png" alt="f:id:hajimari_hamada:20200714164408p:plain" title="f:id:hajimari_hamada:20200714164408p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>上記のメソッドを使って他のメンバーからもらったコメント数を使ってシートに書いています</figcaption></figure></p> <p>1枚目の写真のように SpreadsheetAppクラスのopenByIdメソッドを使って操作したいシートを取得します。 openByIdメソッドではその名の通り、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">スプレッドシート</a>のIDを渡してやる必要があるのですが、 今回も、先ほど出てきたようにプロパティサービスで、プロパティストアで管理してあるシートIDを取得して渡しています。</p> <p>2枚目の写真では、 先ほど取得したシートの中で操作の対象にしたい範囲をgetRangeメソッドで指定したいます。(変数 pullRequestInfoRange)</p> <p>また、シート上の書き込みたいセルを取得するために getCell メソッドを使ってそれぞれ具体的に書き込むセルの絞り込みまでしています。(変数 ~~Cell)</p> <p>実際にこのプルリクで他のメンバーからもらったコメントの合計を取得するために 先ほどgiveCommentCount関数で集計して戻ってきた各メンバーのコメント数が入った配列をgiveCommentCountSum関数に渡しています。 こうして用意したもらったコメントの合計をothersCommentCellにsetValueメソッドで書き込んでいます。</p> <h6>5. Slack チャンネルにメッセージを送信</h6> <p><figure class="figure-image figure-image-fotolife" title="メッセージ送信用関数の一部"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714165751.png" alt="f:id:hajimari_hamada:20200714165751p:plain" title="f:id:hajimari_hamada:20200714165751p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>メッセージ送信用関数の一部</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="処理の結果で返すメッセージを切り替えている部分"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714170029.png" alt="f:id:hajimari_hamada:20200714170029p:plain" title="f:id:hajimari_hamada:20200714170029p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>処理の結果で返すメッセージを切り替えている部分</figcaption></figure></p> <p>最後に処理の結果を知らせるメッセージを対象のSlackチャンネルに返します。 とりあえず、sendMessageという受け取ったメッセージを返す関数を用意し、 処理の結果毎に異なったメッセージを返すようそれぞれのパターンに対してsendMessageを使う関数を用意しています。 2枚目の写真は実際にtry ~ catch でどのメッセージを返す関数を使うか切り分けている部分です。</p> <p>Slack にメッセージを送るために必要な設定は後ほど説明しますが、 送信したいチャンネルにメッセージを送ることができるURLをまたプロパティサービスで取得しています。</p> <p>ここまでがGAS で行われている処理の一連の流れです。</p> <h4>Slack App</h4> <h6>1. slack app を作成</h6> <p><a href="https://api.slack.com/apps">このページ</a>の画面上部のCreate New Appを押す。 名前と入れるworkspace を選ぶダイアログが表示されるので、それぞれ入力し次に進むとひとまずは作成できます。</p> <p>アイコンや名前等の基本的は情報の設定はお好きにどうぞ。</p> <h6>2. <a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a> の設定</h6> <ol> <li>Incoming Webhook を設定</li> </ol> <p>GASプログラムから<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>を通じてチャンネルにメッセージを送るためにはIncoming Webhook を設定する必要があります。 Features/Incoming Webhook を選択し、Activate Incoming Webhooks を On にします。</p> <p><figure class="figure-image figure-image-fotolife" title="Features/Incoming Webhook の選択画面下部"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714175356.png" alt="f:id:hajimari_hamada:20200714175356p:plain" title="f:id:hajimari_hamada:20200714175356p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>Features/Incoming Webhook の選択画面下部</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="Add New Webhook to Workspace 押下後この画面に"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714175842.png" alt="f:id:hajimari_hamada:20200714175842p:plain" title="f:id:hajimari_hamada:20200714175842p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>Add New Webhook to Workspace 押下後この画面に</figcaption></figure></p> <p>そして、画面下部(上記の1枚目の写真)のAdd New Webhook to Workspace を押すと2枚目の写真の画面に遷移します。 追加したい チャンネルを選択し進むと、画面が戻りURLが発行されている事がわかります。</p> <p><figure class="figure-image figure-image-fotolife" title="追加された専用URL"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714175903.png" alt="f:id:hajimari_hamada:20200714175903p:plain" title="f:id:hajimari_hamada:20200714175903p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>追加された専用URL</figcaption></figure></p> <p>このURLをGASのプロパティストアに設定して、上記のsendMessage 部分でプロパティサービスを使って取得して使っています。</p> <ol> <li>Event Subscriptions の設定</li> </ol> <p><figure class="figure-image figure-image-fotolife" title="Features/Event Subscriptions をOn にしています"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714180027.png" alt="f:id:hajimari_hamada:20200714180027p:plain" title="f:id:hajimari_hamada:20200714180027p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>Features/Event Subscriptions をOn にしています</figcaption></figure></p> <p>Incoming Webhook と同じように Event Subscriptions の設定も On にします。 Request URL の部分に<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>公開されているGASのプログラムのURLを入力してテストします。 上で用意した通りにしていれば問題なく設定されます。</p> <p><figure class="figure-image figure-image-fotolife" title="ここでbotに関するイベントを設定することができます"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hajimari_hamada/20200714/20200714180129.png" alt="f:id:hajimari_hamada:20200714180129p:plain" title="f:id:hajimari_hamada:20200714180129p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ここで<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>に関するイベントを設定することができます</figcaption></figure></p> <p>そしてこの写真の部分で、<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a> に関係するイベントの設定ができます。 今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>にメンションをつけるイベントが発生すると、上で設定したURL(GAS)にデータを飛ばしてやりたいので app_mension を選択します。</p> <p>以上で、今回<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>を用意したチャンネルで<a class="keyword" href="http://d.hatena.ne.jp/keyword/bot">bot</a>にメンションをつけた状態でプルリクのURLをメッセージとして送信すると、 GAS側で飛んできたURLを受け取り・集計しシートに反映・返信までできる部分をそれぞれ解説しました。</p> <p>具体的にどういう計算をしたりや、どう言った文言(URL等)が来たら処理を行うのかはお好みでカスタマイズしてみてください。</p> <p>ボットに関しては以上です。</p> <p>また、僕の開発している <a href="https://graspy.jp/">Graspy</a> は若手で活躍されている優秀な方々がキャリアアップできるようなプラットフォームを目指して日々成長しておりますので、ぜひ一度ご覧になってみてください(求人も沢山あります!)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgraspy.jp%2F" title="Graspy(グラスピー)| 若手エースのためのキャリアアップ型転職サイト" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://graspy.jp/">graspy.jp</a></cite></p> <p><a href="https://www.hajimari.inc/">株式会社Hajimari</a>では、自社開発・受託開発を行っています。<br /> 一緒にLaravelやVue.jsを使って開発してくれるエンジニア・デザイナーを絶賛募集しています!</p> <p>新卒・中途どちらも募集しておりますので、興味のある方は以下の記事をぜひ御覧ください!<br /> みなさまとお会いできるのを心からお待ちしております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F83872" title="<1人目UI/UXデザイナー求む>デザインに関わる全ての裁量権を任せます! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/83872">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" title="リードエンジニアを募集!急成長スタートアップ企業で自社サービスを作る!! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> hajimari_hamada 新卒エンジニアがNoCode触って感動した話 hatenablog://entry/26006613587603787 2020-06-23T15:03:28+09:00 2020-06-23T15:03:28+09:00 自己紹介 はじめまして!株式会社Hajimariでエンジニアを担当している保坂(@michael_progs)と申します。 今年の4月に新卒として入社しました。 普段は大きく分けて次の3つの業務を行っています。 inteeという学生のファーストキャリアをサポートするサービスの開発 ソースコードのリファクタリングを行うプロジェクトのリーダー リアルカレッジというプログラミングスクールの企画・運用・講師 今回はプログラミングなしで、サービスを作ることが出来るNoCodeについて書いていきます。 (NoCodeはエンジニアから見てもかなり感動するツールでした) NoCodeとは 読んで字の如く、コー… <h2>自己紹介</h2> <p>はじめまして!<a href="https://www.hajimari.inc/">株式会社Hajimari</a>でエンジニアを担当している保坂(<a href="https://twitter.com/michael_progs">@michael_progs</a>)と申します。<br /> 今年の4月に新卒として入社しました。<br /> 普段は大きく分けて次の3つの業務を行っています。</p> <ol> <li><a href="https://intee.jp/">intee</a>という学生のファーストキャリアをサポートするサービスの開発</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>を行うプロジェクトのリーダー</li> <li>リアルカレッジというプログラミングスクールの企画・運用・講師</li> </ol> <p>今回はプログラミングなしで、サービスを作ることが出来るNoCodeについて書いていきます。<br /> (NoCodeはエンジニアから見てもかなり感動するツールでした)</p> <h2>NoCodeとは</h2> <p>読んで字の如く、コードを書かずにサービスを開発できるツールのことです。</p> <p>例えば何かサービスを作るときに、エンジニアがプログラミングをする必要があると思います。<br /> しかしNoCodeを使えば、プログラミングをせずにサービスを作ることが出来ます!<br /> 今回僕も初めてNoCodeを使ってみたのですが、本当に感動しました。こんなにも短時間でクオリティの高いサービスが出来てしまうのかと。。。</p> <p>ただ現状のNoCodeのツールでは、やはりエンジニアリングの知識が合ったほうがスムーズに開発できるように感じました。<br /> なので、非エンジニアの方がNoCodeを使ってサービスを作るのには、現段階では少しだけハードルが高い気がしました。</p> <p>しかし本当に革新的なツールなので、今後はNoCodeの需要が伸びていきそうです。NoCodeのこれからが楽しみです。</p> <p>僕は今回<a href="https://bubble.io/home">bubble</a>というサービスを使いました。 <a href="https://bubble.io/home">bubble</a> を選択した理由は以下の2つです。(気軽に選びました)</p> <ul> <li>リアルカレッジ受講生が<a href="https://bubble.io/home">bubble</a>を使って、サービスを作ろうとしていたため</li> <li>ネットで検索したときに、<a href="https://bubble.io/home">bubble</a>をおすすめしているサイトが多かったため</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fbubble.io%2Fhome" title="You don&#39;t need to be a coder" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://bubble.io/home">bubble.io</a></cite></p> <h2>なぜNoCodeを触ってみようと思ったか</h2> <p>リアルカレッジ受講生が以下のNoCode<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CF%A5%C3%A5%AB%A5%BD%A5%F3">ハッカソン</a>に出場することになり、僕もその子達を応援したいと思ったからです。 ただ今までNoCodeを一度も触ったことがなかったので、この機会に触ってみようと思い挑戦してみました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fncc01.nocodecamp.co.jp%2F" title="NoCodeCamp杯" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ncc01.nocodecamp.co.jp/">ncc01.nocodecamp.co.jp</a></cite></p> <h2>NoCode触ってみた</h2> <p>実際にNoCodeを使って、ツイートを投稿出来るサービスを作ってみました。開発にかかった時間は60分くらいで、画面を見ながら直感的にサービスを作ることが出来ました。NoCode本当にすごいです!僕が60分で作ったサービスは<a href="https://hajimari-twitter.bubbleapps.io/version-test">こちら</a>になります。</p> <p><figure class="figure-image figure-image-fotolife" title="NoCodeで作ったツイッター"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200620/20200620190217.png" alt="f:id:hosaka555:20200620190217p:plain" title="f:id:hosaka555:20200620190217p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>NoCodeで作った<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C4%A5%A4%A5%C3%A5%BF%A1%BC">ツイッター</a></figcaption></figure></p> <p>ツイートを投稿するだけの簡単なサービスですが、サイトのレスポンスも非常に速く、作っていてとても楽しかったです。 慣れてしまえば10分くらいで、同じようなものが出来てしまいそうです。</p> <p>今回作ったツイートを投稿できるサービスの作り方の手順を簡単に載せておきます。<br /> まず<a href="https://bubble.io/home">bubble</a>を開き、<code>NEW APP</code>よりアプリを新規で作成します。</p> <p><figure class="figure-image figure-image-fotolife" title="NEW APPをクリックしてアプリを作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200622/20200622171320.png" alt="f:id:hosaka555:20200622171320p:plain" title="f:id:hosaka555:20200622171320p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>NEW APPをクリックしてアプリを作成</figcaption></figure></p> <p>次にアプリ名を設定しますが、このアプリ名が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A5%D6%A5%C9%A5%E1%A5%A4%A5%F3">サブドメイン</a>になるので、他で使われていない名前にする必要があります。</p> <p><figure class="figure-image figure-image-fotolife" title="アプリネームを設定"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200622/20200622171346.png" alt="f:id:hosaka555:20200622171346p:plain" title="f:id:hosaka555:20200622171346p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>アプリネームを設定</figcaption></figure></p> <p>早速投稿フォームを作っていきます。(もともとあったエレメント(要素)やコンテナ(ブロック)などは削除しておきました)<br /> 左のメニューから<code>Input forms</code> > <code>Input</code>を選択して、入力フォームを配置します。</p> <p><figure class="figure-image figure-image-fotolife" title="投稿フォームを配置する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200622/20200622174540.png" alt="f:id:hosaka555:20200622174540p:plain" title="f:id:hosaka555:20200622174540p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>投稿フォームを配置する</figcaption></figure></p> <p>次にボタンを配します。先ほどと同様に左のメニューから<code>Visual elements</code> > <code>Button</code> を選択してボタンを配置します。 <figure class="figure-image figure-image-fotolife" title="ボタンを配置する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200622/20200622174910.png" alt="f:id:hosaka555:20200622174910p:plain" title="f:id:hosaka555:20200622174910p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ボタンを配置する</figcaption></figure></p> <p>ボタンの中の<code>edit me</code>をクリックして、<code>投稿</code>に書き換えます。 <figure class="figure-image figure-image-fotolife" title="edit meを投稿に書き換え"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623093706.png" alt="f:id:hosaka555:20200623093706p:plain" title="f:id:hosaka555:20200623093706p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>edit meを投稿に書き換え</figcaption></figure></p> <p>次にDBに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tweet">Tweet</a>テーブルを作成します。左のメニューから、<code>Data</code> を選択し、<code>New Type</code>のところに<code>Tweet</code>と入れて<code>Create</code>をクリックします。</p> <p><figure class="figure-image figure-image-fotolife" title="Tweetテーブルの作成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623094243.png" alt="f:id:hosaka555:20200623094243p:plain" title="f:id:hosaka555:20200623094243p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption><a class="keyword" href="http://d.hatena.ne.jp/keyword/Tweet">Tweet</a>テーブルの作成</figcaption></figure></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Tweet">Tweet</a>テーブルが作成されるので、続いて<code>message</code>というツイートを保存するカラムを追加していきます。 <code>Create a new field</code>をクリックして、以下画像のように設定します。 <figure class="figure-image figure-image-fotolife" title="messageカラムを追加"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623094549.png" alt="f:id:hosaka555:20200623094549p:plain" title="f:id:hosaka555:20200623094549p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>messageカラムを追加</figcaption></figure></p> <p>これで<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tweet">Tweet</a>テーブルの作成は完了です!</p> <p>次に投稿ボタンをクリックしたときにツイートを<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tweet">Tweet</a>テーブルに保存するようにしていきます。 左のメニューから<code>Workflow</code>を選択し、以下画像のように<code>Elements</code> > <code>An element is clicked</code>を選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="ボタンをクリックしたときのイベントを追加"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623094825.png" alt="f:id:hosaka555:20200623094825p:plain" title="f:id:hosaka555:20200623094825p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ボタンをクリックしたときのイベントを追加</figcaption></figure></p> <p>選択後は以下の画像のような感じになります。</p> <p><figure class="figure-image figure-image-fotolife" title="選択後の状態"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623141849.png" alt="f:id:hosaka555:20200623141849p:plain" title="f:id:hosaka555:20200623141849p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>選択後の状態</figcaption></figure></p> <p>ここで上記画像の<code>When</code>をクリックして、出てきたポップアップウィンドウの<code>Element</code>のところに先程追加したボタンを選択します。</p> <p><figure class="figure-image figure-image-fotolife" title="ElementにButton 投稿を選択"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623104834.png" alt="f:id:hosaka555:20200623104834p:plain" title="f:id:hosaka555:20200623104834p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ElementにButton 投稿を選択</figcaption></figure></p> <p>これで投稿ボタンをクリックしたときに何かしらのイベントを実行できるようになります。早速イベントを登録していきます。<br /> <code>Click here to add action</code>をクリックして、<code>Data</code> > <code>Create a new thing</code>を選択してください。</p> <p>その後出てきたポップアップウィンドウに以下のように設定してください。</p> <p><figure class="figure-image figure-image-fotolife" title="ボタンクリック時に登録するデータを設定する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623110416.png" alt="f:id:hosaka555:20200623110416p:plain" title="f:id:hosaka555:20200623110416p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ボタンクリック時に登録するデータを設定する</figcaption></figure></p> <p>これでツイートを投稿ボタンをクリックしたときに、ツイートをDBに登録できるようになりました!<br /> 左のメニューの<code>Data</code> > <code>App data</code> > <code>All Tweets</code>からデータが登録できているか確認できます。</p> <p><figure class="figure-image figure-image-fotolife" title="ツイートを投稿できているか確認"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623110723.png" alt="f:id:hosaka555:20200623110723p:plain" title="f:id:hosaka555:20200623110723p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ツイートを投稿できているか確認</figcaption></figure></p> <p>いよいよ投稿したツイートを表示していきましょう! (実は投稿したツイートを画面に表示出来ずに40分くらい悩みました。。。)</p> <p>まず投稿したツイートを表示するボックスを容易します。<br /> 左のメニューから<code>Container</code> > <code>Repeating Group</code>をクリックして以下画像のように設定してください。</p> <p><figure class="figure-image figure-image-fotolife" title="ツイートを表示するためのボックスを用意する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623135532.png" alt="f:id:hosaka555:20200623135532p:plain" title="f:id:hosaka555:20200623135532p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ツイートを表示するためのボックスを用意する</figcaption></figure></p> <p>次に<code>Repeating Group</code> の上に <code>Text</code> というエレメントを配置して次のように設定します。</p> <p><figure class="figure-image figure-image-fotolife" title="ツイートを表示するためにTextエレメントを配置"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623140518.png" alt="f:id:hosaka555:20200623140518p:plain" title="f:id:hosaka555:20200623140518p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>ツイートを表示するためにTextエレメントを配置</figcaption></figure></p> <p>これで右上の<code>Preview</code>をクリックするとツイートが表示されていると思います!</p> <p><figure class="figure-image figure-image-fotolife" title="投稿したツイートが表示される"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200623/20200623141534.png" alt="f:id:hosaka555:20200623141534p:plain" title="f:id:hosaka555:20200623141534p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>投稿したツイートが表示される</figcaption></figure></p> <p>めでたしめでたし</p> <h2>最後に</h2> <p><a href="https://www.hajimari.inc/">株式会社Hajimari</a>では、自社開発・受託開発を行っています。<br /> 一緒にLaravelやVue.jsを使って開発してくれるエンジニア・デザイナーを絶賛募集しています!</p> <p>新卒・中途どちらも募集しておりますので、興味のある方は以下の記事をぜひ御覧ください!<br /> みなさまとお会いできるのを心からお待ちしております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F83872" title="<1人目UI/UXデザイナー求む>デザインに関わる全ての裁量権を任せます! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/83872">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" title="リードエンジニアを募集!急成長スタートアップ企業で自社サービスを作る!! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> <h2>躓いたところ共有しておきます</h2> <p><figure class="figure-image figure-image-fotolife" title="躓いたところ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200620/20200620190350.png" alt="f:id:hosaka555:20200620190350p:plain" title="f:id:hosaka555:20200620190350p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>躓いたところ</figcaption></figure></p> <p>その原因は次の2つでした。</p> <ol> <li>データを表示するエレメント(要素)を配置していなかったこと</li> <li>データ型を間違えていたこと</li> </ol> <p>まず1つ目についてですが、データベースに存在する値を表示するときは、<code>Repeating Group</code> というエレメント(以下画像の赤色部分)を配置するだけで表示できると思いこんでしまっていました。<br /> 実際には<code>Repeating Group</code> の上に <code>Text</code> というエレメント(以下画像の青色部分)を配置する必要がありました。 ここに気づくまでにかなり時間がかかってしまいました。。。</p> <p><figure class="figure-image figure-image-fotolife" title="データを表示する要素を配置していなかった"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200620/20200620191250.png" alt="f:id:hosaka555:20200620191250p:plain" title="f:id:hosaka555:20200620191250p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>データを表示するエレメントを配置していなかった</figcaption></figure></p> <p>2つ目がデータ型を間違えていたことです。 <br /> 最初僕は<code>Tweet</code>オブジェクトに存在する<code>message</code>というプロパティを表示したかったので、以下画像のように<code>Data source</code>を設定してしまっていました。。。</p> <p><figure class="figure-image figure-image-fotolife" title="データ型を間違えていた"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200620/20200620192312.png" alt="f:id:hosaka555:20200620192312p:plain" title="f:id:hosaka555:20200620192312p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>データ型を間違えていた</figcaption></figure></p> <p>正しくは以下画像のように<code>Data source</code>に設定する値の型を<code>Type of content</code>に合わせて、<code>Text</code>エレメント上で<code>Tweet</code>オブジェクトの<code>message</code>プロパティを表示するように設定することで上手く表示することができました。</p> <p><figure class="figure-image figure-image-fotolife" title="Data sourceに設定する値の型をType of contentに合わせる"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200620/20200620192520.png" alt="f:id:hosaka555:20200620192520p:plain" title="f:id:hosaka555:20200620192520p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>Data sourceに設定する値の型をType of contentに合わせる</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="TextエレメントにTweetオブジェクトのmessageプロパティを表示するように設定する"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/h/hosaka555/20200620/20200620192822.png" alt="f:id:hosaka555:20200620192822p:plain" title="f:id:hosaka555:20200620192822p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>Textエレメントに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Tweet">Tweet</a>オブジェクトのmessageプロパティを表示するように設定する</figcaption></figure></p> <p>でめたしでめたし</p> hosaka555 WordPressでディスク容量がMAXになりカテゴリが表示されなくなった話 hatenablog://entry/26006613585484061 2020-06-16T15:51:54+09:00 2020-06-17T11:36:29+09:00 こんにちは!Hajimariの新卒エンジニアの市川(@shu_chikanne)です。 2020年4月1日に新卒エンジニア2期生としてジョインしました! 普段は、自社プロダクトであるスタートアップ向けマッチングサイト構築パッケージPIECE(https://crowd.itpropartners.com/piece/)の開発や受託開発をしたり、新卒採用関係の業務を行っています! 今回はタイトルにもある通り、WordPressでディスク容量がMAXになったことでカテゴリが表示されなくなった話をしていきたいと思います。 業務の関係上、スクショ等は掲載できないのでご了承ください(><) 今考えても恐… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/g/gattyan27/20200615/20200615205355.jpg" alt="wordpress" title="f:id:gattyan27:20200615205355j:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは!Hajimariの新卒エンジニアの市川(<a href="https://twitter.com/shu_chikanne">@shu_chikanne</a>)です。</p> <p>2020年4月1日に新卒エンジニア2期生としてジョインしました!</p> <p>普段は、自社プロダクトであるスタートアップ向けマッチングサイト構築パッケージPIECE(<a href="https://crowd.itpropartners.com/piece/">https://crowd.itpropartners.com/piece/</a>)の開発や受託開発をしたり、新卒採用関係の業務を行っています!</p> <p><br /> 今回はタイトルにもある通り、<span style="font-size: 110%"><b><a class="keyword" href="http://d.hatena.ne.jp/keyword/WordPress">WordPress</a>でディスク容量がMAXになったことでカテゴリが表示されなくなった話</b></span>をしていきたいと思います。</p> <p>業務の関係上、スクショ等は掲載できないのでご了承ください(><)</p> <p><s>今考えても恐ろしい出来事でした・・・</s></p> <p> </p> <p> </p> <h3>経緯</h3> <p>発端は6月10日の17時頃。</p> <p>クライアントから恐ろしい連絡が飛んできました。内容は以下の2つ。</p> <ul> <li><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EF%A1%BC%A5%C9%A5%D7%A5%EC%A5%B9">ワードプレス</a>のカテゴリーとタグが全てリセットされてしまったこと</p></li> <li><p>それに伴いURLが変わってしまったこと この<a class="keyword" href="http://d.hatena.ne.jp/keyword/WordPress">WordPress</a>のサイトは広告の配信先として利用しており、URLが変わることで広告元のURLが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%EA%A5%F3%A5%AF">デッドリンク</a>になってしまいます。</p></li> </ul> <p><br /> つまり、「<span style="font-size: 110%"><b>大量の広告のリンク先が表示されず、広告経由の収益が出ない状態</b></span>」となっていたのです。</p> <p><br /> 同時接続数が100前後のサイトのため、常時100人近くのユーザーがサイトを閲覧していることになります。</p> <p>そんなサイトのURLが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%EA%A5%F3%A5%AF">デッドリンク</a>になったと聞いて、本気で冷や汗を書きました。</p> <p>もういっそのこと、全てを忘れて眠りにつきたい。。。</p> <p><br /></p> <p>嘆いていても、原因を見つけて修正しないことにはこの状態は解消されません。</p> <p><br /></p> <h3>原因調査</h3> <p>ここからはなぜその状態になったのか、原因調査の段階に入ります。</p> <p>まずはメモリの確認をします。</p> <pre class="code terminal" data-lang="terminal" data-unlink> $ free -m total used free shared buffers cached Mem: 7986 4550 3436 37 142 902 -/+ buffers/cache: 3505 4481 Swap: 2047 14 2033 </pre> <p>ふむ、問題なし。</p> <p>先輩エンジニアからディスク容量はどうなの?と聞かれたので、確認してみると、、、</p> <pre class="code terminal" data-lang="terminal" data-unlink> $ df -h ファイルシス サイズ 使用 残り 使用% マウント位置 devtmpfs 3.9G 60K 3.9G 1% /dev tmpfs 3.9G 0 3.9G 0% /dev/shm /dev/xvda1 30G 30G 0G 100% / [ichikawa@ip-172-31-16-13 ~]$</pre> <p><b>100%!!!</b> <b>はじめてみた!!!</b></p> <p><br /></p> <p>そう、原因は<span style="font-size: 110%"><b>ディスク容量が100%になっていたこと</b></span>でした。</p> <p>ひとまず<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>コンソール上からディスク容量を増やします。(ちなみに一回増やすと減らせません)</p> <p>そしてターミナル上で以下のコマンドを叩きます。</p> <pre class="code terminal" data-lang="terminal" data-unlink> $ sudo growpart /dev/xvda1 $ sudo resize2fs /dev/xvda1 </pre> <p><br /> 確認してみます。</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 3.9G 60K 3.9G 1% /dev tmpfs 3.9G 0 3.9G 0% /dev/shm /dev/xvda1 99G 30G 69G 30% / </pre> <p>しっかり増えてますね。<br /> 今回は30G→100Gまでディスク容量を増やしました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/WordPress">WordPress</a>の管理サイトを確認しに行った所、カテゴリとタグが正常に表示されるようになっていました。<br /> 良かった!!!</p> <h4>なぜディスク容量が100%になっていたのか</h4> <p>duコマンドでどの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リの容量が大きいのか確認してみます。</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ du -sh /* 7.0M /bin 50M /boot 4.0K /cgroup 60K /dev 11M /etc 408K /home 93M /lib 21M /lib64 4.0K /local 16K /lost+found 4.0K /media 4.0K /mnt 43M /opt 107M /root 8.0K /run 12M /sbin 4.0K /selinux 4.0K /srv 2.1G /swap 1.1G /swapfile1 0 /sys 8.0K /tmp 1.3G /usr 24.9G /var </pre> <p></br> なにやらvar配下がとても大きくなっているようです。</p> <p>var配下も確認してみます。</p> <pre class="code terminal" data-lang="terminal" data-unlink>$ cd /var $ du -sh ./* 4.0K ./account 105M ./cache 124K ./db 8.0K ./empty 4.0K ./games 12K ./kerberos 2.4G ./lib 4.0K ./local 16K ./lock 14.8G ./log 0 ./mail 4.0K ./nis 4.0K ./opt 4.0K ./preserve 132K ./run 1.8M ./spool 0 ./swap 4.0K ./tmp 6.7G ./www 4.0K ./yp </pre> <p>log<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リがとても大きいことが判明しました。</p> <p>そしてlog<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを調べていると、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Apache">Apache</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>のログローテーションを設定していないことが判明。</p> <p>(そりゃあディスク容量の大きくなるわ。。。)</p> <p></br> ログローテーションの設定をし後日確認してみた所、/var/log<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リのディスク容量が14.8G→696Mまで減っていました。</p> <p>再発防止策として、<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>の構成の見直しやディスク容量・メモリ・CPU等の監視を行っていく予定です。(ここらへんも今後記事にしていきたいと思います!)</p> <h3>最後に</h3> <p>Hajimariでは、Laravel、vue.js、Nuxt.jsで開発に挑戦したいエンジニア・デザイナーを絶賛募集中です!</p> <p>そして、22卒新卒エンジニアのエントリーも心よりお待ちしております!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F83872" title="<1人目UI/UXデザイナー求む>デザインに関わる全ての裁量権を任せます! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/83872">www.wantedly.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" title="リードエンジニアを募集!急成長スタートアップ企業で自社サービスを作る!! by 株式会社Hajimari(旧:株式会社ITプロパートナーズ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> gattyan27 【スマートロック】sesame APIを使って部屋の鍵を操作する画面を作ってみた hatenablog://entry/26006613405270514 2019-09-18T14:38:06+09:00 2019-09-18T14:38:06+09:00 さなぽんです。 今回は表題の通り、スマートロック「sesame」の鍵を操作してみようかと。 既に機能がある中でAPIを使用するに至った経緯なども含めて書いていきますね。 まずは今回の主役となるsesameの紹介です。 <p>さなぽんです。<br /> 今回は表題の通り、スマートロック「<a class="keyword" href="http://d.hatena.ne.jp/keyword/sesame">sesame</a>」の鍵を操作してみようかと。<br /> 既に機能がある中で<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を使用するに至った経緯なども含めて書いていきますね。<br /> まずは今回の主役となる<a class="keyword" href="http://d.hatena.ne.jp/keyword/sesame">sesame</a>の紹介です。</p> <ul class="table-of-contents"> <li><a href="#sesame-スマートロックとは">sesame スマートロックとは</a></li> <li><a href="#今回やりたかったこと">今回やりたかったこと</a></li> <li><a href="#API実装">API実装</a><ul> <li><a href="#API-Key取得">API Key取得</a></li> <li><a href="#API-実行">API 実行</a><ul> <li><a href="#スマートロック一覧取得">スマートロック一覧取得</a></li> <li><a href="#スマートロック状態取得">スマートロック状態取得</a></li> <li><a href="#スマートロック開閉">スマートロック開閉</a></li> <li><a href="#スマートロック開閉処理結果取得">スマートロック開閉処理結果取得</a></li> </ul> </li> <li><a href="#view">view</a><ul> <li><a href="#php">php</a></li> <li><a href="#css">css</a></li> <li><a href="#出来上がり">出来上がり</a></li> </ul> </li> <li><a href="#さいごに">さいごに</a></li> </ul> </li> </ul> <div class="section"> <h3 id="sesame-スマートロックとは"><a class="keyword" href="http://d.hatena.ne.jp/keyword/sesame">sesame</a> スマートロックとは</h3> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fjp.candyhouse.co%2F" title="セサミ スマートロック | CANDY HOUSE JAPAN" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://jp.candyhouse.co/">jp.candyhouse.co</a></cite></p><p>色々と機能があって便利そう!<br /> また、使い方などの疑問に対する記事がブログには豊富に上がっているので<br /> 困った時には非常に役に立ちそうですね。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fameblo.jp%2Fcandyhouse-inc%2F" title="SESAME (セサミ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ameblo.jp/candyhouse-inc/">ameblo.jp</a></cite></p><p></p> </div> <div class="section"> <h3 id="今回やりたかったこと">今回やりたかったこと</h3> <p>イメージはシェアハウスで鍵を共有する、という体です。<br /> 公式サイトには鍵のシェアもできるよ!とあります。<br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>でシェアができたらなあ、と思い問い合わせをしてみましたところ<br /> 以下のようなお返事が届きました。</p> <blockquote> <p>現在<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を使って出来る事は以下の通りです。</p> <ol> <li>セサミに施錠・解錠の命令を送る</li> <li>セサミの施錠・解錠ステータスの確認</li> <li>セサミのバッテリーレベルの確認</li> </ol><p><a href="https://docs.candyhouse.co/?shell#sesame-api">CANDY HOUSE Developer Reference</a></p><br /> <p>鍵のシェアについては現在<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>では搭載されておりませんので、アプリにて行っていただく必要がございます。</p> </blockquote> <p>> 鍵のシェアについては現在<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>では搭載されておりません<br /> んー、残念。。</p><p>少ない台数のスマートロックであればそこまで大変ではないですが<br /> 複数のシェアハウスで鍵を管理する、他のサイトと連動する、となった場合はちょっと煩いそう。。。</p><p>ということで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を使用し画面も作ってみよう。<br /> との流れになります。<br /> <br /> </p> </div> <div class="section"> <h3 id="API実装"><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>実装</h3> <p>ここからは既に<a class="keyword" href="http://d.hatena.ne.jp/keyword/sesame">sesame</a>購入済、アプリにて<a class="keyword" href="http://d.hatena.ne.jp/keyword/wifi">wifi</a>ポイント設定済みで<br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/sesame">sesame</a>が既に動く、という前提で進めていきます。</p><p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fameblo.jp%2Fcandyhouse-inc%2Fentry-12332339299.html" title="『WiFiアクセスポイントを設定する』" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ameblo.jp/candyhouse-inc/entry-12332339299.html">ameblo.jp</a></cite></p><p></p> <div class="section"> <h4 id="API-Key取得"><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> Key取得</h4> <p>まずは<a href="https://my.candyhouse.co:CANDY HOUSE Dashboard">https://my.candyhouse.co:CANDY HOUSE Dashboard</a>からログインし<br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> Settings画面から<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を使用する為の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> Keyを発行し、<br /> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> Keyを<a class="keyword" href="http://d.hatena.ne.jp/keyword/sesame">sesame</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>用のconfigファイルに記述します。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synStatement">return</span> <span class="synSpecial">[</span> <span class="synConstant">&quot;Sesame&quot;</span><span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synComment">// API Url</span> <span class="synConstant">&quot;ApiUrl&quot;</span> <span class="synStatement">=&gt;</span> <span class="synConstant">&quot;https://api.candyhouse.co/public/&quot;</span> , <span class="synComment">// API Key</span> <span class="synConstant">&quot;Authorization&quot;</span> <span class="synStatement">=&gt;</span> <span class="synConstant">&quot;発行されたAPI Key&quot;</span> , <span class="synSpecial">]</span> <span class="synSpecial">]</span>; </pre> </div> <div class="section"> <h4 id="API-実行"><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a> 実行</h4> <div class="section"> <h5 id="スマートロック一覧取得">スマートロック一覧取得</h5> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synStatement">$</span><span class="synIdentifier">http</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> Client<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">url</span> <span class="synStatement">=</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.ApiUrl'</span><span class="synSpecial">)</span> <span class="synStatement">.</span> <span class="synConstant">'sesames/'</span>; <span class="synStatement">$</span><span class="synIdentifier">response</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">http</span><span class="synType">-&gt;</span>get<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">url</span>, <span class="synSpecial">[]</span>, <span class="synSpecial">[</span><span class="synConstant">'headers'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synConstant">'Authorization'</span> <span class="synStatement">=&gt;</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.Authorization'</span><span class="synSpecial">)</span>, <span class="synSpecial">]])</span>; <span class="synStatement">$</span><span class="synIdentifier">list</span> <span class="synStatement">=</span> <span class="synIdentifier">json_decode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">response</span><span class="synType">-&gt;</span>body<span class="synSpecial">())</span>; </pre><p>取得結果</p> <pre class="code lang-json" data-lang="json" data-unlink> <span class="synSpecial">[</span> <span class="synSpecial">{</span> &quot;<span class="synStatement">device_id</span>&quot;: &quot;<span class="synConstant">00000000-0000-0000-0000-000000000000</span>&quot;, &quot;<span class="synStatement">serial</span>&quot;: &quot;<span class="synError">ABC1234567</span>&quot; &quot;<span class="synStatement">nickname</span>&quot;: &quot;<span class="synConstant">Front door</span>&quot;<span class="synError">,</span> <span class="synError"> }</span>, <span class="synSpecial">{</span> &quot;<span class="synStatement">device_id</span>&quot;: &quot;<span class="synConstant">00000000-0000-0000-0000-000000000001</span>&quot;, &quot;<span class="synStatement">serial</span>&quot;: &quot;<span class="synError">DEF7654321</span>&quot; &quot;<span class="synStatement">nickname</span>&quot;: &quot;<span class="synConstant">Back door</span>&quot;<span class="synError">,</span> <span class="synError"> }</span> ] </pre> </div> <div class="section"> <h5 id="スマートロック状態取得">スマートロック状態取得</h5> <p>スマートロックのデ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>IDをパラメータに入れて取得します。<br /> <br /> </p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synStatement">$</span><span class="synIdentifier">http</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> Client<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">url</span> <span class="synStatement">=</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.ApiUrl'</span><span class="synSpecial">)</span> <span class="synStatement">.</span> <span class="synConstant">&quot;sesames/</span><span class="synSpecial">{</span><span class="synStatement">$</span>スマートロック デバイスID<span class="synSpecial">}</span><span class="synConstant">&quot;</span>; <span class="synStatement">$</span><span class="synIdentifier">response</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">http</span><span class="synType">-&gt;</span>get<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">url</span>, <span class="synSpecial">[]</span>, <span class="synSpecial">[</span><span class="synConstant">'headers'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synConstant">'Authorization'</span> <span class="synStatement">=&gt;</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.Authorization'</span><span class="synSpecial">)</span>, <span class="synSpecial">]])</span>; <span class="synStatement">$</span><span class="synIdentifier">isLocked</span> <span class="synStatement">=</span> <span class="synIdentifier">json_decode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">response</span><span class="synType">-&gt;</span>body<span class="synSpecial">())</span><span class="synType">-&gt;</span>locked; </pre><p>取得結果</p> <pre class="code lang-json" data-lang="json" data-unlink> <span class="synSpecial">{</span> &quot;<span class="synStatement">locked</span>&quot;: <span class="synConstant">true</span>, &quot;<span class="synStatement">battery</span>&quot;: <span class="synConstant">100</span>, &quot;<span class="synStatement">responsive</span>&quot;: <span class="synConstant">true</span> <span class="synSpecial">}</span> </pre> </div> <div class="section"> <h5 id="スマートロック開閉">スマートロック開閉</h5> <p>これもスマートロックのデ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%A4%A5%B9">バイス</a>IDをパラメータに入れて取得します。<br /> リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トメソッドがpostになります。<br /> 問い合わせの回答にある 「1. セサミに施錠・解錠の命令を送る」にあたる箇所となります。<br /> この部分は<a class="keyword" href="http://d.hatena.ne.jp/keyword/ajax">ajax</a>で処理をする体です。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synStatement">$</span><span class="synIdentifier">http</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> Client<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">url</span> <span class="synStatement">=</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.ApiUrl'</span><span class="synSpecial">)</span> <span class="synStatement">.</span> <span class="synConstant">&quot;sesames/</span><span class="synSpecial">{</span><span class="synStatement">$</span>スマートロック デバイスID<span class="synSpecial">}</span><span class="synConstant">&quot;</span>; <span class="synStatement">$</span><span class="synIdentifier">data</span> <span class="synStatement">=</span> <span class="synSpecial">[</span><span class="synConstant">'command'</span> <span class="synStatement">=&gt;</span> <span class="synStatement">$</span><span class="synIdentifier">_POST</span><span class="synSpecial">[</span><span class="synConstant">'to_lock'</span><span class="synSpecial">]</span> <span class="synStatement">==</span> <span class="synConstant">'true'</span> <span class="synStatement">?</span> <span class="synConstant">'lock'</span> <span class="synStatement">:</span> <span class="synConstant">'unlock'</span><span class="synSpecial">]</span>; <span class="synStatement">$</span><span class="synIdentifier">response</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">http</span><span class="synType">-&gt;</span>post<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">url</span>, <span class="synIdentifier">json_encode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">data</span><span class="synSpecial">)</span>, <span class="synSpecial">[</span> <span class="synConstant">'headers'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synConstant">'Authorization'</span> <span class="synStatement">=&gt;</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.Authorization'</span><span class="synSpecial">)</span>, <span class="synSpecial">]</span>, <span class="synSpecial">])</span>; <span class="synStatement">$</span><span class="synIdentifier">state</span> <span class="synStatement">=</span> <span class="synIdentifier">json_decode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">response</span><span class="synType">-&gt;</span>body<span class="synSpecial">())</span>; </pre><p>取得結果</p> <pre class="code lang-json" data-lang="json" data-unlink> <span class="synSpecial">{</span> &quot;<span class="synStatement">task_id</span>&quot;: &quot;<span class="synConstant">01234567-890a-bcde-f012-34567890abcd</span>&quot; <span class="synSpecial">}</span> </pre> </div> <div class="section"> <h5 id="スマートロック開閉処理結果取得">スマートロック開閉処理結果取得</h5> <p>開閉処理の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>はあくまでも命令を送るのみなので<br /> その命令で返ってきたtask_idの内容を別の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>処理で確認します。<br /> スマートロック開閉処理に引き続き行います。<br /> <br /> </p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synSpecial">&lt;?php</span> <span class="synComment">// 処理結果確認</span> <span class="synStatement">do</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">result</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">this</span><span class="synType">-&gt;</span>getActionResult<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">taskId</span><span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">while</span> <span class="synSpecial">(</span><span class="synIdentifier">json_decode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">result</span><span class="synType">-&gt;</span>body<span class="synSpecial">())</span><span class="synType">-&gt;</span>status <span class="synStatement">!=</span> <span class="synConstant">'terminated'</span><span class="synSpecial">)</span>; <span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synIdentifier">json_decode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">result</span><span class="synType">-&gt;</span>body<span class="synSpecial">())</span><span class="synType">-&gt;</span>successful <span class="synStatement">==</span> <span class="synConstant">false</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">throw</span> <span class="synPreProc">new</span> HttpException<span class="synSpecial">(</span><span class="synIdentifier">json_decode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">result</span><span class="synType">-&gt;</span>body<span class="synSpecial">())</span><span class="synType">-&gt;</span>error<span class="synSpecial">)</span>; <span class="synSpecial">}</span> <span class="synStatement">$</span><span class="synIdentifier">status</span> <span class="synStatement">=</span> <span class="synIdentifier">json_decode</span><span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">result</span><span class="synType">-&gt;</span>body<span class="synSpecial">())</span><span class="synType">-&gt;</span>status; <span class="synComment">// ちょっと見辛いですが、do whileのループをprivateメソッド化しています。</span> <span class="synType">private</span> <span class="synPreProc">function</span> getActionResult<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">taskId</span><span class="synSpecial">)</span> <span class="synSpecial">{</span> <span class="synStatement">$</span><span class="synIdentifier">http</span> <span class="synStatement">=</span> <span class="synPreProc">new</span> Client<span class="synSpecial">()</span>; <span class="synStatement">$</span><span class="synIdentifier">resultUrl</span> <span class="synStatement">=</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.ApiUrl'</span><span class="synSpecial">)</span> <span class="synStatement">.</span> <span class="synConstant">&quot;action-result?task_id=</span><span class="synSpecial">{</span><span class="synStatement">$</span><span class="synIdentifier">taskId</span><span class="synSpecial">}</span><span class="synConstant">&quot;</span>; <span class="synStatement">$</span><span class="synIdentifier">responseResult</span> <span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">http</span><span class="synType">-&gt;</span>get<span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">resultUrl</span>, <span class="synSpecial">[]</span>, <span class="synSpecial">[</span><span class="synConstant">'headers'</span> <span class="synStatement">=&gt;</span> <span class="synSpecial">[</span> <span class="synConstant">'Authorization'</span> <span class="synStatement">=&gt;</span> Configure<span class="synStatement">::</span>read<span class="synSpecial">(</span><span class="synConstant">'Sesame.Authorization'</span><span class="synSpecial">)</span>, <span class="synSpecial">]])</span>; <span class="synStatement">return</span> <span class="synStatement">$</span><span class="synIdentifier">responseResult</span>; <span class="synSpecial">}</span> </pre><p><br /> 取得結果</p> <pre class="code lang-json" data-lang="json" data-unlink> <span class="synSpecial">{</span> &quot;<span class="synStatement">status</span>&quot;: &quot;<span class="synConstant">terminated</span>&quot;, &quot;<span class="synStatement">successful</span>&quot;: <span class="synConstant">true</span> <span class="synSpecial">}</span> </pre> </div> </div> <div class="section"> <h4 id="view">view</h4> <div class="section"> <h5 id="php"><a class="keyword" href="http://d.hatena.ne.jp/keyword/php">php</a></h5> <p>こちらは参考までに。</p> <pre class="code lang-php" data-lang="php" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;loading hide&quot;</span><span class="synIdentifier">&gt;&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;container title&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;row&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;col-md-12&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">h3</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;text-center&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span>スマートキー<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">h3</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;container&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;row&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;col-md-12 text-center&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;error_message&quot;</span><span class="synIdentifier"> </span><span class="synType">id</span><span class="synIdentifier">=</span><span class="synConstant">&quot;error_message&quot;</span><span class="synIdentifier"> </span><span class="synType">style</span><span class="synIdentifier">=</span><span class="synConstant">&quot;display:none;&quot;</span><span class="synIdentifier">&gt;</span>エラーが発生しました。再度操作してもエラーとなる場合、管理までお問い合わせください。<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;text-info&quot;</span><span class="synIdentifier">&gt;</span>下記ボタンをタップして開閉して下さい<span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">div</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;col-md-12 text-center&quot;</span><span class="synIdentifier">&gt;</span> <span class="synSpecial">&lt;?php</span> <span class="synStatement">if</span> <span class="synSpecial">(</span><span class="synStatement">$</span><span class="synIdentifier">isLocked</span><span class="synSpecial">)</span> <span class="synStatement">:</span> <span class="synSpecial">?&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">button</span><span class="synIdentifier"> </span><span class="synType">type</span><span class="synIdentifier">=</span><span class="synConstant">&quot;button&quot;</span><span class="synIdentifier"> </span><span class="synType">id</span><span class="synIdentifier">=</span><span class="synConstant">&quot;button&quot;</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;btn btn-warning btn-circle&quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">i</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;fas fa-lock&quot;</span><span class="synIdentifier">&gt;&lt;/</span><span class="synStatement">i</span><span class="synIdentifier">&gt;&lt;</span><span class="synStatement">br</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span>しまっています<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">button</span><span class="synIdentifier">&gt;</span> <span class="synSpecial">&lt;?php</span> <span class="synStatement">else</span> <span class="synStatement">:</span> <span class="synSpecial">?&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">button</span><span class="synIdentifier"> </span><span class="synType">type</span><span class="synIdentifier">=</span><span class="synConstant">&quot;button&quot;</span><span class="synIdentifier"> </span><span class="synType">id</span><span class="synIdentifier">=</span><span class="synConstant">&quot;button&quot;</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;btn btn-info btn-circle &quot;</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">i</span><span class="synIdentifier"> </span><span class="synType">class</span><span class="synIdentifier">=</span><span class="synConstant">&quot;fas fa-lock-open&quot;</span><span class="synIdentifier">&gt;&lt;/</span><span class="synStatement">i</span><span class="synIdentifier">&gt;&lt;</span><span class="synStatement">br</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span>あいています<span class="synIdentifier">&lt;/</span><span class="synStatement">p</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">button</span><span class="synIdentifier">&gt;</span> <span class="synSpecial">&lt;?php</span> <span class="synStatement">endif</span>; <span class="synSpecial">?&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">div</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synSpecial"> $</span>(<span class="synConstant">'#button'</span>)<span class="synSpecial">.on</span>(<span class="synConstant">'click'</span><span class="synSpecial">, </span><span class="synIdentifier">function</span>()<span class="synSpecial"> </span><span class="synIdentifier">{</span> <span class="synSpecial"> $.ajax</span>(<span class="synIdentifier">{</span> <span class="synSpecial"> type: </span><span class="synConstant">'POST'</span><span class="synSpecial">,</span> <span class="synSpecial"> url: </span><span class="synConstant">'指定のURL'</span><span class="synSpecial">,</span> <span class="synSpecial"> data: </span><span class="synIdentifier">{</span> <span class="synSpecial"> to_lock : </span><span class="synConstant">&quot;</span><span class="synSpecial">&lt;?</span><span class="synStatement">=</span> <span class="synStatement">$</span><span class="synIdentifier">isLocked</span> <span class="synStatement">?</span> <span class="synConstant">'false'</span> <span class="synStatement">:</span> <span class="synConstant">'true'</span> <span class="synSpecial">?&gt;</span><span class="synConstant">&quot;</span><span class="synSpecial">,</span> <span class="synSpecial"> </span><span class="synIdentifier">}</span><span class="synSpecial">,</span> <span class="synSpecial"> beforeSend: </span><span class="synIdentifier">function</span>()<span class="synIdentifier">{</span> <span class="synSpecial"> $</span>(<span class="synConstant">'.loading'</span>)<span class="synSpecial">.removeClass</span>(<span class="synConstant">'hide'</span>)<span class="synSpecial">;</span> <span class="synSpecial"> </span><span class="synIdentifier">}</span><span class="synSpecial">,</span> <span class="synSpecial"> </span><span class="synIdentifier">}</span>) <span class="synSpecial"> .done</span>(<span class="synSpecial"> </span>(<span class="synSpecial">data</span>)<span class="synSpecial"> =&gt; </span><span class="synIdentifier">{</span> <span class="synSpecial"> console.log</span>(<span class="synSpecial">data</span>)<span class="synSpecial">;</span> <span class="synSpecial"> </span><span class="synStatement">window</span><span class="synSpecial">.</span><span class="synStatement">location</span><span class="synSpecial">.reload</span>(<span class="synConstant">true</span>)<span class="synSpecial">;</span> <span class="synSpecial"> </span><span class="synIdentifier">}</span>) <span class="synSpecial"> .fail</span>(<span class="synSpecial"> </span>(<span class="synSpecial">data</span>)<span class="synSpecial"> =&gt; </span><span class="synIdentifier">{</span> <span class="synSpecial"> console.log</span>(<span class="synSpecial">data</span>)<span class="synSpecial">;</span> <span class="synSpecial"> $</span>(<span class="synConstant">'#error_message'</span>)<span class="synSpecial">.css</span>(<span class="synConstant">'display'</span><span class="synSpecial">, </span><span class="synConstant">'block'</span>)<span class="synSpecial">;</span> <span class="synSpecial"> $</span>(<span class="synConstant">'.loading'</span>)<span class="synSpecial">.addClass</span>(<span class="synConstant">'hide'</span>)<span class="synSpecial">;</span> <span class="synSpecial"> </span><span class="synIdentifier">}</span>) <span class="synSpecial"> </span><span class="synIdentifier">}</span>)<span class="synSpecial">;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> </pre> </div> <div class="section"> <h5 id="css"><a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a></h5> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>も参考までに。</p> <pre class="code lang-css" data-lang="css" data-unlink><span class="synIdentifier">.hide</span> <span class="synIdentifier">{</span> <span class="synType">display</span>: <span class="synConstant">none</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">.loading</span> <span class="synIdentifier">{</span> <span class="synType">position</span>: <span class="synConstant">fixed</span>; <span class="synType">top</span>: <span class="synConstant">0</span>; <span class="synType">right</span>: <span class="synConstant">0</span>; <span class="synType">bottom</span>: <span class="synConstant">0</span>; <span class="synType">left</span>: <span class="synConstant">0</span>; <span class="synType">z-index</span>: <span class="synConstant">9999</span>; <span class="synType">background</span>: <span class="synIdentifier">rgba(</span><span class="synConstant">0</span><span class="synIdentifier">,</span><span class="synConstant">0</span><span class="synIdentifier">,</span><span class="synConstant">0</span><span class="synIdentifier">,</span><span class="synConstant">.5</span><span class="synIdentifier">)</span>; <span class="synType">background-image</span>: <span class="synIdentifier">url(</span><span class="synConstant">/images/loading.gif</span><span class="synIdentifier">)</span>; <span class="synType">background-repeat</span>: <span class="synConstant">no-repeat</span>; <span class="synType">background-attachment</span>: <span class="synConstant">fixed</span>; <span class="synType">background-position</span>: <span class="synConstant">center</span> <span class="synConstant">center</span>; <span class="synType">background-size</span>: <span class="synConstant">150px</span> <span class="synConstant">150px</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">.btn-circle</span> <span class="synIdentifier">{</span> <span class="synType">width</span>: <span class="synConstant">200px</span>; <span class="synType">height</span>: <span class="synConstant">200px</span>; <span class="synType">padding</span>: <span class="synConstant">10px</span> <span class="synConstant">16px</span>; <span class="synType">font-size</span>: <span class="synConstant">40px</span>; <span class="synType">line-height</span>: <span class="synConstant">1.33</span>; <span class="synType">border-radius</span>: <span class="synConstant">100px</span>; <span class="synType">margin-top</span>: <span class="synConstant">30px</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">.btn-circle</span> <span class="synStatement">p</span> <span class="synIdentifier">{</span> <span class="synType">font-size</span>: <span class="synConstant">14px</span>; <span class="synType">color</span>: <span class="synConstant">#fff</span>; <span class="synType">margin-top</span>: <span class="synConstant">10px</span>; <span class="synIdentifier">}</span> <span class="synIdentifier">.btn-circle.btn-secondary</span> <span class="synStatement">p</span> <span class="synIdentifier">{</span> <span class="synType">font-size</span>: <span class="synConstant">14px</span>; <span class="synType">color</span>: <span class="synConstant">#575757</span>; <span class="synType">margin-top</span>: <span class="synConstant">10px</span>; <span class="synIdentifier">}</span> </pre> </div> <div class="section"> <h5 id="出来上がり">出来上がり</h5> <p>画面としてはこんな感じです。</p><p><figure class="figure-image figure-image-fotolife" title="スマートロック操作画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sanapon1020/20190825/20190825211630.png" alt="" title="f:id:sanapon1020:20190825211630p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>スマートロック操作画面</figcaption></figure></p><p></p> </div> </div> <div class="section"> <h4 id="さいごに">さいごに</h4> <p>結構簡単に実装ができました。<br /> これから<a class="keyword" href="http://d.hatena.ne.jp/keyword/sesame">sesame</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>もできることが増えていくとは思いますので<br /> 今後の展開にも期待していきたいところです。</p><p>この記事がどなたかのお役に立てたのなら嬉しいですね。<br /> 良きスマートロックライフを!</p> </div> </div> sanapon1020 Laravel好きが高じてアメリカまで行ってきました!LaraconUSまとめ(一日目) hatenablog://entry/26006613377273125 2019-08-05T18:39:37+09:00 2019-08-05T21:39:17+09:00 こんにちわ、ITプロパートナーズのエンジニアのくまモンエンジニア(@miyakey7)です。 Laraconに関する記事を書きたいと思います。多分今年出席した日本人は私しかいなかったような気がします。 Laravelは自分の転職のきっかけになったフレームワークで思い入れがあります。 元々組み込みエンジニアで通信ライブラリとか作っていた私が、去年Laravelが好きでというかLaravelしか知らないような状態からWEBエンジニアになんとか転職し1年以上経ちました。 今年、そして来年と開催されるLaravel jp conferenceもコアスタッフとしてお手伝いさせていただいてます。 conf… <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725100354p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725100354.png" alt="f:id:kumamon_engineer:20190725100354p:plain" /></p> <p> </p> <p>こんにちわ、ITプロパートナーズのエンジニアの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア(<a href="https://twitter.com/miyakey7" target="_blank">@miyakey7</a>)です。</p> <p>Laraconに関する記事を書きたいと思います。多分今年出席した日本人は私しかいなかったような気がします。</p> <p>Laravelは自分の転職のきっかけになった<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>で思い入れがあります。</p> <p>元々組み込みエンジニアで通信ライブラリとか作っていた私が、去年Laravelが好きでというかLaravelしか知らないような状態からWEBエンジニアになんとか転職し1年以上経ちました。</p> <p>今年、そして来年と開催される<strong>Laravel jp conference</strong>もコアスタッフとしてお手伝いさせていただいてます。</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Laravel JP Conference 2020" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fconference2020.laravel.jp%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://conference2020.laravel.jp/">conference2020.laravel.jp</a></cite></p> <p> </p> <p>そんなLaravel好きが高じて、ついには国境を越えてしまいました。</p> <p>今年の7/24〜25日に開催された<a href="https://laracon.us/"> </a>laraconUS(<a href="https://laracon.us/">https://laracon.us/</a>)に参加するためにNYに行ってきました!</p> <p>私の目から見たLaraconUSの感想を率直に書いていきたいと思います!</p> <p>英語力が皆無のため、多分間違ってる所があるかと思います、すいません!</p> <p>誤りがあればご指摘お願い致します!!</p> <p> </p> <ul class="table-of-contents"> <li><a href="#入場">入場</a></li> <li><a href="#午前セッション">午前セッション</a><ul> <li><a href="#ADAM-WATHAN">ADAM WATHAN</a></li> <li><a href="#FREEK-VAN-DER-HERTEN">FREEK VAN DER HERTEN</a></li> <li><a href="#休憩">休憩</a></li> <li><a href="#BOBBY-ELITE-BOUWMANN">BOBBY ELITE BOUWMANN</a></li> <li><a href="#JMAC">JMAC</a></li> </ul> </li> <li><a href="#ランチタイム">ランチタイム</a></li> <li><a href="#午後セッション">午後セッション</a><ul> <li><a href="#KEITH-DAMIANI">KEITH DAMIANI</a></li> <li><a href="#KAYA-THOMAS">KAYA THOMAS</a></li> <li><a href="#JONATHAN-REININK">JONATHAN REININK</a></li> <li><a href="#JUSTIN-JACKSON">JUSTIN JACKSON</a></li> <li><a href="#TAYLOR-OTWELL">TAYLOR OTWELL</a></li> </ul> </li> <li><a href="#AFTER-PARTY--MINGLING">AFTER PARTY &amp; MINGLING</a></li> </ul> <h3 id="入場">入場</h3> <p>まずは朝8:30から入場開始です。</p> <p> </p> <blockquote class="twitter-tweet" data-lang="HASH(0x55974acaa120)"> <p dir="ltr" lang="ja">あと1時間くらいしたら向かいます! <a href="https://t.co/MAxJgORBKI">https://t.co/MAxJgORBKI</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1153993274941693954?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> <p>会場は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%BA%A5%B9%A5%AF%A5%A8%A5%A2">タイムズスクエア</a>近くのイケイケの場所です!</p> <p>NYは以前も訪れたことがあり、とても好きな街でした。</p> <p>今年のLaraconUSがNYと決まった瞬間に行くことを即決しました。</p> <p> </p> <p class="section-hero-header-title-subtitle" style="background: #ffffff; border: 0px; border-radius: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: inherit; font-stretch: inherit; font-size: 13px; line-height: inherit; font-family: Roboto, 'Noto Sans JP', Arial, sans-serif; list-style: none; margin: 4px 0px 0px; outline: 0px; overflow: visible; padding: 0px; vertical-align: baseline; color: #000000; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/PlayStation">PlayStation</a> Theater</strong></p> <p> <a href="https://www.google.com/maps?q=playstation+theater&amp;um=1&amp;ie=UTF-8&amp;sa=X&amp;ved=0ahUKEwjn_aKw9M7jAhWUX80KHdIBCSMQ_AUIEigC">https://www.google.com/maps?q=playstation+theater&amp;um=1&amp;ie=UTF-8&amp;sa=X&amp;ved=0ahUKEwjn_aKw9M7jAhWUX80KHdIBCSMQ_AUIEigC</a></p> <p> </p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725102557j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725102557.jpg" alt="f:id:kumamon_engineer:20190725102557j:plain" /></p> <p>ゴリゴリの強面SPに荷物検査をされ、金属探知機を通って、震えながら蛍光色光る地下のフロアへ。</p> <p> </p> <blockquote class="twitter-tweet" data-lang="HASH(0x55974a5fb810)"> <p dir="ltr" lang="ja">完全に雰囲気に飲み込まれて震えてるなう<a href="https://twitter.com/hashtag/laracon?src=hash&amp;ref_src=twsrc%5Etfw">#laracon</a> <a href="https://t.co/GnGAoSv1Jd">pic.twitter.com/GnGAoSv1Jd</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154015451661447168?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>中はもう騒然とした雰囲気でおしゃべりで陽気なエンジニアたちが大騒ぎでした。</p> <p>朝からみんな元気良いな...</p> <p> </p> <p>今回のLaraconは昔の2Dゲームの世界観がテーマのようです。</p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725102748j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725102748.jpg" alt="f:id:kumamon_engineer:20190725102748j:plain" /></p> <p><br />ストⅡもありました👀</p> <p>こちらは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D1%A1%BC%A5%D5%A5%A1%A5%DF%A5%B3%A5%F3">スーパーファミコン</a>(スーパー<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%CB%A5%F3%A5%C6%A5%F3%A5%C9%A1%BC">ニンテンドー</a>)ですね。</p> <p>知らない外人と戦って2戦2勝でした。eスポーツイケますね。</p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725102833j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725102833.jpg" alt="f:id:kumamon_engineer:20190725102833j:plain" /></p> <p> </p> <p>会場に入ると既にかなり埋まってる状態。</p> <p>座席は自由です。</p> <p> </p> <p>空いてる座席にグッズが置いてありました!</p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725103719j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725103719.jpg" alt="f:id:kumamon_engineer:20190725103719j:plain" /></p> <p>中身は意外とシンプルでした。Laraconはあんまりこだわらないのかな?</p> <blockquote class="twitter-tweet" data-lang="HASH(0x556f4e2a33f8)"> <p dir="ltr" lang="ja">本日、貰ったグッズ!新ロゴステッカーは明日貰えるのかな🙃<br /> <a href="https://twitter.com/hashtag/Laracon?src=hash&amp;ref_src=twsrc%5Etfw">#Laracon</a> <a href="https://t.co/S2PCeFJROF">pic.twitter.com/S2PCeFJROF</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154190532517748737?ref_src=twsrc%5Etfw">July 25, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> <p>座席はかなり後方でした..出遅れた!</p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725103545j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725103545.jpg" alt="f:id:kumamon_engineer:20190725103545j:plain" /></p> <p>会場の<a class="keyword" href="http://d.hatena.ne.jp/keyword/wi-fi">wi-fi</a>のパスワードがわからず弱弱の<a class="keyword" href="http://d.hatena.ne.jp/keyword/wi-fi">wi-fi</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E2%A5%D0%A5%A4%A5%EB%A5%EB%A1%BC%A5%BF%A1%BC">モバイルルーター</a>苦戦していたら、公式ツイートでアナウンスが!</p> <blockquote class="twitter-tweet" data-lang="HASH(0x561fcf898220)"> <p dir="ltr" lang="en">Welcome everyone! Our <a class="keyword" href="http://d.hatena.ne.jp/keyword/WiFi">WiFi</a> code is “artisan19”… let’s <a class="keyword" href="http://d.hatena.ne.jp/keyword/tweet">tweet</a> using the <a href="https://twitter.com/hashtag/Laracon?src=hash&amp;ref_src=twsrc%5Etfw">#Laracon</a> hash tag! 🤙</p> — Laracon US (@LaraconUS) <a href="https://twitter.com/LaraconUS/status/1154010714169888769?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> Laravelerなら誰でも胸アツなパスワード「artisan19」に胸が熱くなりましました!</p> <p> </p> <h3 id="午前セッション">午前セッション</h3> <h4 id="ADAM-WATHAN">ADAM WATHAN</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Adam Wathan (@adamwathan) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Fadamwathan" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/adamwathan">twitter.com</a></cite></p> <p>自身が開発しているtailwind <a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>についてのセッション</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Tailwind CSS - A utility-first CSS framework for rapidly building custom designs" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftailwindcss.com%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://tailwindcss.com/">tailwindcss.com</a></cite></p> <p> </p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725113912j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725113912.jpg" alt="f:id:kumamon_engineer:20190725113912j:plain" /></p> <p>基本コードを書きながらレイアウトのデモをしまくっていました。</p> <p>場馴れしてるからなのか余裕で話しながらガンガンコード書いてて凄いな〜と感じました。900人が見てる中でコーディングとか震える。</p> <p> </p> <p>tailwind <a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>はとても拡張性があり、容易なレイアウト置き換えが出来たりレスポンシブ対応していたりする便利<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>でした。</p> <p>Laravelと関与する所はbladeの中でも書きやすいよ〜くらいの感じでした。</p> <p>bladeを適宜<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8">コンポーネント</a>化してincludeで呼び出し、パラメータ引数としてクラス設定することで容易に変更出来るよねと。</p> <h4 id="FREEK-VAN-DER-HERTEN">FREEK VAN DER HERTEN</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Freek Van der Herten 🎆 (@freekmurze) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Ffreekmurze" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/freekmurze">twitter.com</a></cite></p> <p> </p> <p>引用はこのツイートにまとまってます</p> <blockquote class="twitter-tweet" data-lang="HASH(0x555bc251c938)"> <p dir="ltr" lang="en">Hope you liked my <a class="keyword" href="http://d.hatena.ne.jp/keyword/talk">talk</a> at <a href="https://twitter.com/LaraconUS?ref_src=twsrc%5Etfw">@laraconUS</a> 🗣<br /><br />Some interesting links for you:<a href="https://t.co/YL59aS6Spa">https://t.co/YL59aS6Spa</a><a href="https://t.co/FVR0SsXHRp">https://t.co/FVR0SsXHRp</a><a href="https://t.co/zagELDvzIG">https://t.co/zagELDvzIG</a><a href="https://t.co/JKlDi45hJe">https://t.co/JKlDi45hJe</a><a href="https://t.co/Vn9NE6OCiv">https://t.co/Vn9NE6OCiv</a><a href="https://t.co/UCrbNS3Wp8">https://t.co/UCrbNS3Wp8</a><a href="https://t.co/0IuAXLis3v">https://t.co/0IuAXLis3v</a><br /><br />Slides: <a href="https://t.co/XEBGrWbXfg">https://t.co/XEBGrWbXfg</a></p> — Freek Van der Herten 🎆 (@freekmurze) <a href="https://twitter.com/freekmurze/status/1154042185416790017?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> <p>観客と和気あいあいとしながら、こちらも基本ライブコーディングをしていたのが印象的。</p> <blockquote class="twitter-tweet" data-lang="HASH(0x5629325d3500)"> <p dir="ltr" lang="ja">文字サイズ大きくしろってめっちゃ煽られてる<a href="https://twitter.com/hashtag/laracon?src=hash&amp;ref_src=twsrc%5Etfw">#laracon</a> <a href="https://t.co/pNzf9u3P1e">pic.twitter.com/pNzf9u3P1e</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154029974950752256?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> アクションをコントローラやモデルに書かず、アクションフォルダ配下に置く。</p> <blockquote class="twitter-tweet" data-lang="HASH(0x55aa5f648550)"> <p dir="ltr" lang="en">Interesting idea from <a href="https://twitter.com/freekmurze?ref_src=twsrc%5Etfw">@freekmurze</a> and the <a href="https://twitter.com/spatie_be?ref_src=twsrc%5Etfw">@spatie_be</a> team - putting reusable actions in a separate "Actions" directory rather than storing them on a model or controller. I'll have to give that a whirl. <a href="https://twitter.com/hashtag/laracon?src=hash&amp;ref_src=twsrc%5Etfw">#laracon</a> <a href="https://twitter.com/hashtag/laravel?src=hash&amp;ref_src=twsrc%5Etfw">#laravel</a> <a href="https://t.co/3GZUSL9n34">pic.twitter.com/3GZUSL9n34</a></p> — Kyle Barney (@KyleAndCode) <a href="https://twitter.com/KyleAndCode/status/1154032484415356928?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> <p>viewに関してはviewModelを使った書き方を説明。</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="spatie/laravel-view-models" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fspatie%2Flaravel-view-models" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://github.com/spatie/laravel-view-models">github.com</a></cite></p> <p>viewに渡すパラメータを沢山並べるとかもしやってるならば参考にしましょう。</p> <p> </p> <p>続いてbladeの話。blade-xを使えばincludeの書き方を省略できるよと。</p> <p>確かにそうですね。vueライクな書き方です。</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="spatie/laravel-blade-x" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fspatie%2Flaravel-blade-x" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://github.com/spatie/laravel-blade-x">github.com</a></cite></p> <p> </p> <h4 id="休憩">休憩</h4> <p>休憩で一度全員が席を離れます。</p> <p>そのチャンスを使って前の方に移動しました!</p> <p>みんな意外と前の方の席にこだわりはなかったみたいで、すんなり座れました。</p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190725115110j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190725/20190725115110.jpg" alt="f:id:kumamon_engineer:20190725115110j:plain" /></p> <p>ただ、この前方エアコンがめちゃくちゃ効いていて寒かった。。 </p> <p> </p> <h4 id="BOBBY-ELITE-BOUWMANN">BOBBY ELITE BOUWMANN</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Bobby Bouwmann (@bobbybouwmann) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Fbobbybouwmann" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/bobbybouwmann">twitter.com</a></cite></p> <p>laravelの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%B6%A5%A4%A5%F3%A5%D1%A5%BF%A1%BC%A5%F3">デザインパターン</a>の話</p> <p><iframe id="talk_frame_531269" style="border: 0; padding: 0; margin: 0; background: transparent;" src="//speakerdeck.com/player/768fae57c85542d385dfebbdafeadf39" width="710" height="399" frameborder="0" allowfullscreen="allowfullscreen"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/bobbybouwmann/laravel-design-patterns-2-dot-0">speakerdeck.com</a></cite></p> <p>・Singleton Pattern</p> <p>・Observer Pattern</p> <p>・Bridge Pattern</p> <p>についてピザとピザ窯を例に話していました。サンプルコードも載っているので</p> <p>わかりやすいと思います!</p> <p> </p> <p>印象的だったのは最後の方に言っていた</p> <p><em><strong>「No silver bullet, learn by doing and trying(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B6%E4%A4%CE%C3%C6%B4%DD">銀の弾丸</a>はない、やってみることで学ぶ)」</strong></em></p> <p>という部分。</p> <p>設計に関しては本当にそう感じます。どれだけ向き合ってどれだけプロダクトを落とし込んで適切な設計ができるか。一朝一夕で身につくものではないと感じます。</p> <p> </p> <h4 id="JMAC">JMAC</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Jason McCreary (@gonedark) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Fgonedark" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/gonedark">twitter.com</a></cite></p> <p> laravelのちょっと気の利いた書き方というセッションでした。</p> <p><img class="hatena-fotolife" title="f:id:kumamon_engineer:20190805152851j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kumamon_engineer/20190805/20190805152851.jpg" alt="f:id:kumamon_engineer:20190805152851j:plain" /></p> <p>結構色んな省略形の話をしていました。</p> <p>それこそbladeであれば <strong>@auth</strong> や<strong>@guest</strong>、<strong>@<a class="keyword" href="http://d.hatena.ne.jp/keyword/csrf">csrf</a></strong>みたいな所やModelリレーションの<strong>Pivots</strong>や<strong>MorphMap</strong>の書き方、<strong>redirect</strong>に<strong>withInput</strong>付けるなど。結構基礎的な使い方が多かった印象。</p> <p><strong>Gate</strong>の話がちらっと出たりもしましたが(もう少し丁寧に聞きたかった)、全体的に公式リファレンスにある内容でしたね。</p> <p> </p> <h3 id="ランチタイム">ランチタイム</h3> <p> ランチタイムになると900人が一斉に外に出ます。</p> <p>人の流れとかガン無視カオス状態です。</p> <p>ちなみに会場は映画館のシアターとロビーと通路があるだけのイメージなので、</p> <p>全員が飛び出すととんでもない人の渋滞が発生します。</p> <p>そんな事は全く気にしません、これが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カ。</p> <p>ランチボックスと飲み物をカウンターで貰いあとは自由に食べてねという形式。</p> <p>通路に座り込んで食べる人が半分、シアターの中で食べる人が半分ぐらいでした。</p> <p>なんで中で食べないんだろう?</p> <p> </p> <p>そして配られたランチボックスが完全に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カ!</p> <blockquote class="twitter-tweet" data-lang="HASH(0x55af1f619a28)"> <p dir="ltr" lang="ja">ランチタイムに配られたランチボックス。<br />謎の巻き物とポテチとりんごとチョコチップクッキーにスプライト。<br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カか!<br /> <a href="https://twitter.com/hashtag/laracon?src=hash&amp;ref_src=twsrc%5Etfw">#laracon</a> <a href="https://t.co/DJPmNUaEP2">pic.twitter.com/DJPmNUaEP2</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154063333345103872?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> <h3 id="午後セッション">午後セッション</h3> <h4 id="KEITH-DAMIANI">KEITH DAMIANI</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Keith Damiani (@keithdamiani) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Fkeithdamiani" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/keithdamiani">twitter.com</a></cite></p> <p>GraphQLに関するセッションでした</p> <p><iframe id="talk_frame_535596" style="border: 0; padding: 0; margin: 0; background: transparent;" src="//speakerdeck.com/player/7e4681e0bc39420ebdd45fda271cc5b4" width="710" height="399" frameborder="0" allowfullscreen="allowfullscreen"></iframe><cite class="hatena-citation"><a href="https://speakerdeck.com/damiani/connecting-the-dots-graph-databases-and-laravel">speakerdeck.com</a></cite></p> <p>最もスライドが作り込まれたセッションでした。</p> <p>データの繋がりをニューヨークの地下鉄の路線図を例にして表現し、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B0%A5%E9%A5%D5%CD%FD%CF%C0">グラフ理論</a>の歴史を示した上で、どういう時にGraphQLの優位性が発揮されるかについて述べています。</p> <h4 id="KAYA-THOMAS"> <br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/KAYA">KAYA</a> THOMAS</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Kaya Thomas (@kthomas901) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Fkthomas901" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/kthomas901">twitter.com</a></cite></p> <p>彼女のセッションはサイドプロジェクトの走らせ方についてでした。</p> <p>プログラミングの話は一切なく、マインドやマネジメント、ケアの方法、終わらせ方など一連を実体験を元に話をしていました。</p> <blockquote class="twitter-tweet" data-lang="HASH(0x5596f9dd39b0)"> <p dir="ltr" lang="en">Motivating speech from <a href="https://twitter.com/kthomas901?ref_src=twsrc%5Etfw">@kthomas901</a> at <a href="https://twitter.com/hashtag/LaraconUS?src=hash&amp;ref_src=twsrc%5Etfw">#LaraconUS</a> “Get your work out into the world” <a href="https://t.co/FeZTe5k3Hi">pic.twitter.com/FeZTe5k3Hi</a></p> — Guvener Gokce (@guvener) <a href="https://twitter.com/guvener/status/1154096582905782272?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> サイドプロジェクトを始めたらちゃんと広めることも大事だよと。</p> <h4 id="JONATHAN-REININK"> <br />JONATHAN REININK</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Jonathan Reinink (@reinink) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Freinink" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/reinink">twitter.com</a></cite></p> <p>こちらのコースの作者ですね。みんな大好きEloquentに関するセッションです!</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Eloquent Performance Patterns - Premium video course by Jonathan Reinink" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Freinink.ca%2Feloquent-course%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://reinink.ca/eloquent-course/">reinink.ca</a></cite></p> <p>内容はパフォーマンスチューニングの方法、テクニック、debugbarの数値確認方法などでした。</p> <p>結果的には</p> <blockquote class="twitter-tweet" data-lang="HASH(0x564b4daaad80)"> <p dir="ltr" lang="ja">副問い合わせちゃんと使った方が速い事多いよという話だった。<br />addSubSelectか。<br /> <a href="https://twitter.com/hashtag/laracon?src=hash&amp;ref_src=twsrc%5Etfw">#laracon</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154104028516835330?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> でした。</p> <p>まぁ、最終的にこれに至るまでにメモリのサイズを確認したり、クエリの数を確認して少しずつチューニングしていくという手順のデモがこのセッションの有意義な部分だったと思います。そしてEloquentは何も知らないと簡単にN+1などを実装出来てしまうので、しっかりとした知識と結果のクエリをよく確認しようというお話ですね。</p> <h4 id="JUSTIN-JACKSON"> <br />JUSTIN JACKSON</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Justin Jackson (@mijustin) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Fmijustin" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/mijustin">twitter.com</a></cite></p> <p>今回のLaraconのメインMCをやっていました。めちゃくちゃ話し上手で盛り上げ上手。</p> <p>それもそのはずで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DD%A5%C3%A5%C9%A5%AD%A5%E3%A5%B9%A5%C8">ポッドキャスト</a>の配信者でもあります。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/SaaS">SaaS</a>開発に関する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DD%A5%C3%A5%C9%A5%AD%A5%E3%A5%B9%A5%C8">ポッドキャスト</a>です。</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Build Your SaaS – bootstrapping in 2019" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsaas.transistor.fm%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://saas.transistor.fm/">saas.transistor.fm</a></cite></p> <p>スライドはこちら!</p> <p>内容は人生とプログラミングを学ぶことについてでした。結構<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AA%A4%C1%A4%E3%A4%E9%A4%B1">おちゃらけ</a>た感じでMCやってましたがセッションは真面目でした。</p> <p><a href="https://justinjackson.ca/assets/laracon-2019-compressed.pdf">https://justinjackson.ca/assets/laracon-2019-compressed.pdf</a></p> <p>最初にLaraconの会場にいる参加者の年代を聞いてましたが、圧倒的に30代が多かったです!<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カのPHPerは30代が多いぞ!(日本はどうなんだ?)</p> <p> </p> <p>「<em><strong>"It's too late for me to learn programming". This emotional baggage held me back.</strong></em>(「プログラミングを学ぶには遅すぎる」という感情が自分を引き留めていた。)」</p> <p>これはプログラミングだけでなく色んな新しいチャレンジに言えることだと思う。</p> <p>気にせずに飛び込んでチャレンジすればいい。</p> <h4 id="TAYLOR-OTWELL"> <br />TAYLOR OTWELL</h4> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Taylor Otwell 🏝 (@taylorotwell) | Twitter" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftwitter.com%2Ftaylorotwell" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://twitter.com/taylorotwell">twitter.com</a></cite></p> <p>遂に出ました、大トリ。<strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/CREATOR">CREATOR</a> OF LARAVEL</strong></p> <p>Laravelの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%CF%BB%CF%BC%D4">創始者</a>Taylorです。</p> <p> セッションは早速Laravel6リリースの公式発表!</p> <p>ロゴも変わり、会場のボルテージも上がりました!</p> <blockquote class="twitter-tweet" data-lang="HASH(0x55ddebe0af98)"> <p dir="ltr" lang="ja">ロゴが変わったー!<br /> <a href="https://twitter.com/hashtag/laracon?src=hash&amp;ref_src=twsrc%5Etfw">#laracon</a> <a href="https://t.co/8PgcPBHlZ0">pic.twitter.com/8PgcPBHlZ0</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154125582243893248?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> ロゴが変わるために、カンファレンスTシャツはこのセッションが終わるまで配られませんでした。途中で帰る人とか気にしないのね...。</p> <blockquote class="twitter-tweet" data-lang="HASH(0x5567dfab0060)"> <p dir="ltr" lang="ja">Tシャツまだ配られてなかったのは新ロゴだったからか <a href="https://t.co/MvrGOb3NkE">pic.twitter.com/MvrGOb3NkE</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154126277495967746?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> ロゴ及び公式サイトが変わること、8月にリリースされること、ver6からセマンティックバージョニング(semver)というバージョン方式を取るという話がありました。</p> <p>これでLaravel6の話は終わります。</p> <p>そう、今回のセッションのメインはLaravel6の発表ではなく、Laravel VaporというLaravel用のサーバーレスデプロイプラットフォームの発表でした。</p> <p> </p> <p>これは去年発表されたNova同様に有料になっております。</p> <p>元々あったLaravel Forgeという環境管理ツールがあり、その中にあったlaravel-cloudという<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サービスと連携していた部分のコードを書き直し、独立進化し<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>と連携したサーバーレスプラットフォームに昇華したイメージでしょうか。</p> <p> </p> <p>ちなみに先日、laravel-cloudは一瞬公開されましたが</p> <blockquote class="twitter-tweet" data-lang="HASH(0x55c0c976e5d8)"> <p dir="ltr" lang="en">I have mentioned Laravel Cloud, an abandoned attempt to build a "Forge Pro", several times. This project was eventually replaced by Vapor. However, the entire backend was completely finished. I have opened the source here if anyone is interested: <a href="https://t.co/8RQ9xhsSLO">https://t.co/8RQ9xhsSLO</a></p> — Taylor Otwell 🏝 (@taylorotwell) <a href="https://twitter.com/taylorotwell/status/1157029040462671875?ref_src=twsrc%5Etfw">August 1, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> すぐにprivateに戻されましたw</p> <blockquote class="twitter-tweet" data-lang="HASH(0x555a4bc79108)"> <p dir="ltr" lang="en">Had to make Laravel Cloud private again. Amazingly, too much BS and whining about me even doing something as simple as sharing that code. Have a good weekend. 🤷‍♂️</p> — Taylor Otwell 🏝 (@taylorotwell) <a href="https://twitter.com/taylorotwell/status/1157346751738712064?ref_src=twsrc%5Etfw">August 2, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> 大丈夫かなw</p> <p> </p> <p>まぁ、Vaporに関してはすぐに使う機会は無さそうという感覚です。</p> <p>新規で作る際に使うのもありかと思う一方でVaporのコストを足す分の利点が今の所感じれていないという印象。</p> <p>アプローチの方向性は面白いし今後の進化を期待したいと思います。</p> <p> </p> <p>Vaporの詳細については以下にわかりやすくまとまっています。</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Introducing Laravel Vapor" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmattstauffer.com%2Fblog%2Fintroducing-laravel-vapor%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://mattstauffer.com/blog/introducing-laravel-vapor/">mattstauffer.com</a></cite></p> <p> </p> <p>既に公式サイトも公開されています。</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Laravel Vapor - Serverless PHP Platform" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fvapor.laravel.com%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://vapor.laravel.com/">vapor.laravel.com</a></cite></p> <p> </p> <p>また本セッションは他に先駆けて動画配信もされているので詳細はこちらを参考にしていただければと思います。</p> <p><iframe src="https://www.youtube.com/embed/XsPeWjKAUt0?feature=oembed" width="480" height="270" frameborder="0" allowfullscreen=""></iframe><cite class="hatena-citation"><a href="https://youtu.be/XsPeWjKAUt0">youtu.be</a></cite></p> <p> </p> <h3 id="AFTER-PARTY--MINGLING">AFTER PARTY &amp; MINGLING</h3> <p> そして遂に運命の時が...</p> <p>Laravelの作者Taylorとの対面!</p> <p> </p> <p>とりあえず記念写真!</p> <blockquote class="twitter-tweet" data-lang="HASH(0x56535901ae90)"> <p dir="ltr" lang="ja">テイラーと写真撮れた! <a href="https://t.co/MGnGQp6wYt">pic.twitter.com/MGnGQp6wYt</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154148609287426049?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> <p>そこからは緊張の会話タイム...!</p> <p> </p> <p>日本から来た話、日本のカンファレンスのスタッフだよとアピール(初日に着ていたのはLaravel JP conferenceのコアスタッフTシャツ)、内緒の交渉、Laravelの今後について、Vaporの話も少し...短い間でしたが色々と聞けてとても貴重な時間でした。</p> <p> </p> <p>これで完全に満足した私は、懇親会で数人と会話にならない会話をして帰りました。</p> <p>「日本から来たの?」「何故?」「日本でLaravelはどうなんだい?」などなど、聞き取りやすい質問はサクッと返事出来るのですが、少しでも聞き取れなくなるともうお手上げ状態でした...。</p> <p> </p> <p>ちなみに懇親会も人の配置はガン無視で通路やロビーに突然食べ物や飲み物が置かれたり、スタッフが配り続けたりして立ったままガンガン話すだけでした..<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カ凄いぜ。</p> <blockquote class="twitter-tweet" data-lang="HASH(0x55a250387688)"> <p dir="ltr" lang="ja">混沌としているのでそろそろ離脱します!また明日Laracon! <a href="https://t.co/pvRM1ioKy8">pic.twitter.com/pvRM1ioKy8</a></p> — <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AF%A4%DE%A5%E2%A5%F3">くまモン</a>エンジニア (@miyakey7) <a href="https://twitter.com/miyakey7/status/1154172746282295301?ref_src=twsrc%5Etfw">July 24, 2019</a></blockquote> <p> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p> </p> <p>次回、二日目編は会場にたどり着くまでに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%BA%A5%B9%A5%AF%A5%A8%A5%A2">タイムズスクエア</a>で黒人に捕まって60ドル払った話を中心にお送りする予定です😢</p> <p> </p> <p>すいません、上記は事実なのですが、二日目もちゃんと書きます!!</p> <p> </p> <p>Laravel好きは一度お話してみませんか?</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="リードエンジニアを募集!急成長スタートアップ企業で自社サービスを作る!! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> <p> </p> kumamon_engineer 社内の新制度(Googleのあのルール??)を使い、社内データのJuliaで統計分析PJ(仮)を1人で勝手にキックオフしてみました hatenablog://entry/17680117127211103601 2019-07-04T14:52:09+09:00 2019-07-04T16:18:26+09:00 こんにちは!ITプロパートナーズの新卒エンジニアの栗岡です。 2019年4月1日に新卒2期生、エンジニアとしては1人目のエンジニアとしてジョインしました。 業務では、Larave/Vue.js/JQueryでITプロパートナーズメインサイトの開発と20卒21卒の新卒採用担当をしています! 今回は、会社の新ルールAd-vanを使い、社内データの統計分析PJ(仮)を1人で勝手に始めましたというお話を自分を鼓舞する意味を込めてしようと思います。 本当は5月にはキックオフしていた事は秘密です。 そもそもAd-vanとは?? ITプロパートナーズの新しい制度です。 毎週水曜日にそれぞれの社員が考えた「今… <p> </p> <p><img class="hatena-fotolife" title="f:id:marron-web-engineer:20190704161804p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/marron-web-engineer/20190704/20190704161804.png" alt="f:id:marron-web-engineer:20190704161804p:plain" /><br /><br /></p> <p>こんにちは!ITプロパートナーズの新卒エンジニアの栗岡です。</p> <p>2019年4月1日に新卒2期生、エンジニアとしては1人目のエンジニアとしてジョインしました。</p> <p>業務では、Larave/Vue.js/<a class="keyword" href="http://d.hatena.ne.jp/keyword/JQuery">JQuery</a>で<span style="color: #3f3f3f; font-family: -apple-system, system-ui, 'Helvetica Neue', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', '游ゴシック Medium', meiryo, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; float: none; display: inline !important;">ITプロパートナーズ</span><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%A4%A5%F3%A5%B5%A5%A4%A5%C8" style="background: #ffffff; color: inherit !important; overflow-wrap: break-word; text-decoration: none; border-bottom: 1px dotted #eceef1; font-size: 15px; font-weight: 400; font-style: normal; pointer-events: auto !important; cursor: pointer !important; font-family: -apple-system, system-ui, 'Helvetica Neue', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', '游ゴシック Medium', meiryo, sans-serif; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;">メインサイト</a><span style="color: #3f3f3f; font-family: -apple-system, system-ui, 'Helvetica Neue', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', '游ゴシック Medium', meiryo, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; float: none; display: inline !important;">の開発と20卒21卒の新卒採用担当をしています!</span></p> <p> </p> <p>今回は、会社の新ルールAd-vanを使い、社内データの統計分析PJ(仮)を1人で勝手に始めましたというお話を<span style="color: #2196f3;">自分を鼓舞する意味</span>を込めてしようと思います。</p> <p><span style="text-decoration: line-through;">本当は5月にはキックオフしていた事は秘密です。</span></p> <p> </p> <p><span style="font-size: 150%;"><strong>そもそもAd-vanとは??</strong></span></p> <p>ITプロパートナーズの新しい制度です。</p> <p>毎週水曜日に<strong>それぞれの社員が考えた「今やりたいこと、今やるべきこと」に 主体的 に取り組んでもらう</strong>という制度です。</p> <p>エンジニアにとっては凄く嬉しい制度で、自社サービスにtypescript導入している社員やR&amp;Dプロジェクトを進めるエンジニア がいます。</p> <p>今回はこの制度を使い、簡単ではありますがデータ分析に挑戦してみようと思いました。</p> <p>(専攻が心理系だったことから、統計分析は研究のためにふんわりと使っていた背景もあり、、)</p> <p> </p> <p><span style="font-size: 150%;"><strong>やりたい事</strong></span></p> <p><span style="color: #2196f3;"><strong>主要事業であるITプロパートナーズのデータや20卒採用で取れたデータを統計分析という形で処理し、データを取得し、ただ眺めていただけの状態から、統計的に何が言えるのかまで出せるようにしたい。</strong></span></p> <p><span style="text-decoration: underline;"><strong>そして、いつか統計に強いエンジニアやサイエンティストにジョインして欲しい。</strong></span></p> <p>僕自身が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C5%FD%B7%D7%B3%D8">統計学</a>に精通しているわけでも、数学がゴリゴリでできるわけではないので、</p> <p>「取得しているデータを活用する」するための一歩になれば良いというモチベーションで緩くやって行きますw</p> <p> </p> <p><span style="font-size: 150%;"><strong>使用ツール・言語</strong></span></p> <ul> <li><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"><a href="https://jupyter.org/">Jupyter Notebook</a> </span></li> <li><a href="https://julialang.org/">Julia</a></li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Google%20%A5%B9%A5%D7%A5%EC%A5%C3%A5%C9%A5%B7%A1%BC%A5%C8">Google スプレッドシート</a></li> </ul> <p> </p> <p>言語はぶっちゃけなんでも良かったのですが、データ分析と言えば<a class="keyword" href="http://d.hatena.ne.jp/keyword/Python">Python</a>!R!っていうのが何となく嫌で、あまのじゃく精神からJuliaにしました。</p> <p> </p> <p><span style="font-size: 24px;"><strong>デモ</strong></span></p> <p>今回は試しに、Juliaと<span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif;">Jupyter Notebookを使った回帰分析とグラフ表示をファッションサイトを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EC%A5%A4%A5%D4%A5%F3%A5%B0">スクレイピング</a>して取得したデータで試しに行いました。</span></p> <p> </p> <p>今回は、<strong>身長が高いモデルさんほどファッションサイトではランキング上位に来るのか?つまり、背が高い人ほど洋服が似合うのか??</strong>を検定しました。</p> <p><span style="color: #2196f3;">ランキングの順位に対して、身長に因果関係があるのかを判定します。</span></p> <p> </p> <p><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif;"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%DE%A5%F3%A5%C9%A5%E9%A5%A4%A5%F3">コマンドライン</a>で Jupyter Notebookを起動</span></p> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 14.4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <pre style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; display: block; padding: 0px; margin: 0px; font-size: inherit; line-height: 1.8; word-break: break-all; overflow-wrap: break-word; color: inherit; background-color: transparent; border: none; border-radius: 0px;"><span class="p" style="box-sizing: inherit; color: #e3e3e3;"> jupyter notebook<br /></span></pre> </div> <p> </p> <p> あとは以下のようにコードを書き込む。(y=ax+bに当てはめるだけ)</p> <p>ほぼRの上位互換のようなコードですね。</p> <p><img class="hatena-fotolife" title="f:id:marron-web-engineer:20190630171721p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/m/marron-web-engineer/20190630/20190630171721.png" alt="f:id:marron-web-engineer:20190630171721p:plain" /></p> <p> <script>// <![CDATA[ (function(N){var k=/[\.\/]/,L=/\s*,\s*/,C=function(a,d){return a-d},a,v,y={n:{}},M=function(){for(var a=0,d=this.length;a&lt;d;a++)if("undefined"!=typeof this[a])return this[a]},A=function(){for(var a=this.length;--a;)if("undefined"!=typeof this[a])return this[a]},w=function(k,d){k=String(k);var f=v,n=Array.prototype.slice.call(arguments,2),u=w.listeners(k),p=0,b,q=[],e={},l=[],r=a;l.firstDefined=M;l.lastDefined=A;a=k;for(var s=v=0,x=u.length;s&lt;x;s++)"zIndex"in u[s]&amp;&amp;(q.push(u[s].zIndex),0&gt;u[s].zIndex&amp;&amp; (e[u[s].zIndex]=u[s]));for(q.sort(C);0&gt;q[p];)if(b=e[q[p++] ],l.push(b.apply(d,n)),v)return v=f,l;for(s=0;s&lt;x;s++)if(b=u[s],"zIndex"in b)if(b.zIndex==q[p]){l.push(b.apply(d,n));if(v)break;do if(p++,(b=e[q[p] ])&amp;&amp;l.push(b.apply(d,n)),v)break;while(b)}else e[b.zIndex]=b;else if(l.push(b.apply(d,n)),v)break;v=f;a=r;return l};w._events=y;w.listeners=function(a){a=a.split(k);var d=y,f,n,u,p,b,q,e,l=[d],r=[];u=0;for(p=a.length;u&lt;p;u++){e=[];b=0;for(q=l.length;b&lt;q;b++)for(d=l[b].n,f=[d[a[u] ],d["*"] ],n=2;n--;)if(d= f[n])e.push(d),r=r.concat(d.f||[]);l=e}return r};w.on=function(a,d){a=String(a);if("function"!=typeof d)return function(){};for(var f=a.split(L),n=0,u=f.length;n&lt;u;n++)(function(a){a=a.split(k);for(var b=y,f,e=0,l=a.length;e&lt;l;e++)b=b.n,b=b.hasOwnProperty(a[e])&amp;&amp;b[a[e] ]||(b[a[e] ]={n:{}});b.f=b.f||[];e=0;for(l=b.f.length;e&lt;l;e++)if(b.f[e]==d){f=!0;break}!f&amp;&amp;b.f.push(d)})(f[n]);return function(a){+a==+a&amp;&amp;(d.zIndex=+a)}};w.f=function(a){var d=[].slice.call(arguments,1);return function(){w.apply(null, [a,null].concat(d).concat([].slice.call(arguments,0)))}};w.stop=function(){v=1};w.nt=function(k){return k?(new RegExp("(?:\\.|\\/|^)"+k+"(?:\\.|\\/|$)")).test(a):a};w.nts=function(){return a.split(k)};w.off=w.unbind=function(a,d){if(a){var f=a.split(L);if(1&lt;f.length)for(var n=0,u=f.length;n&lt;u;n++)w.off(f[n],d);else{for(var f=a.split(k),p,b,q,e,l=[y],n=0,u=f.length;n&lt;u;n++)for(e=0;e&lt;l.length;e+=q.length-2){q=[e,1];p=l[e].n;if("*"!=f[n])p[f[n] ]&amp;&amp;q.push(p[f[n] ]);else for(b in p)p.hasOwnProperty(b)&amp;&amp; q.push(p[b]);l.splice.apply(l,q)}n=0;for(u=l.length;n&lt;u;n++)for(p=l[n];p.n;){if(d){if(p.f){e=0;for(f=p.f.length;e&lt;f;e++)if(p.f[e]==d){p.f.splice(e,1);break}!p.f.length&amp;&amp;delete p.f}for(b in p.n)if(p.n.hasOwnProperty(b)&amp;&amp;p.n[b].f){q=p.n[b].f;e=0;for(f=q.length;e&lt;f;e++)if(q[e]==d){q.splice(e,1);break}!q.length&amp;&amp;delete p.n[b].f}}else for(b in delete p.f,p.n)p.n.hasOwnProperty(b)&amp;&amp;p.n[b].f&amp;&amp;delete p.n[b].f;p=p.n}}}else w._events=y={n:{}}};w.once=function(a,d){var f=function(){w.unbind(a,f);return d.apply(this, arguments)};return w.on(a,f)};w.version="0.4.2";w.toString=function(){return"You are running Eve 0.4.2"};"undefined"!=typeof module&amp;&amp;module.exports?module.exports=w:"function"===typeof define&amp;&amp;define.amd?define("eve",[],function(){return w}):N.eve=w})(this); (function(N,k){"function"===typeof define&amp;&amp;define.amd?define("Snap.svg",["eve"],function(L){return k(N,L)}):k(N,N.eve)})(this,function(N,k){var L=function(a){var k={},y=N.requestAnimationFrame||N.webkitRequestAnimationFrame||N.mozRequestAnimationFrame||N.oRequestAnimationFrame||N.msRequestAnimationFrame||function(a){setTimeout(a,16)},M=Array.isArray||function(a){return a instanceof Array||"[object Array]"==Object.prototype.toString.call(a)},A=0,w="M"+(+new Date).toString(36),z=function(a){if(null== a)return this.s;var b=this.s-a;this.b+=this.dur*b;this.B+=this.dur*b;this.s=a},d=function(a){if(null==a)return this.spd;this.spd=a},f=function(a){if(null==a)return this.dur;this.s=this.s*a/this.dur;this.dur=a},n=function(){delete k[this.id];this.update();a("mina.stop."+this.id,this)},u=function(){this.pdif||(delete k[this.id],this.update(),this.pdif=this.get()-this.b)},p=function(){this.pdif&amp;&amp;(this.b=this.get()-this.pdif,delete this.pdif,k[this.id]=this)},b=function(){var a;if(M(this.start)){a=[]; for(var b=0,e=this.start.length;b&lt;e;b++)a[b]=+this.start[b]+(this.end[b]-this.start[b])*this.easing(this.s)}else a=+this.start+(this.end-this.start)*this.easing(this.s);this.set(a)},q=function(){var l=0,b;for(b in k)if(k.hasOwnProperty(b)){var e=k[b],f=e.get();l++;e.s=(f-e.b)/(e.dur/e.spd);1&lt;=e.s&amp;&amp;(delete k[b],e.s=1,l--,function(b){setTimeout(function(){a("mina.finish."+b.id,b)})}(e));e.update()}l&amp;&amp;y(q)},e=function(a,r,s,x,G,h,J){a={id:w+(A++).toString(36),start:a,end:r,b:s,s:0,dur:x-s,spd:1,get:G, set:h,easing:J||e.linear,status:z,speed:d,duration:f,stop:n,pause:u,resume:p,update:b};k[a.id]=a;r=0;for(var K in k)if(k.hasOwnProperty(K)&amp;&amp;(r++,2==r))break;1==r&amp;&amp;y(q);return a};e.time=Date.now||function(){return+new Date};e.getById=function(a){return k[a]||null};e.linear=function(a){return a};e.easeout=function(a){return Math.pow(a,1.7)};e.easein=function(a){return Math.pow(a,0.48)};e.easeinout=function(a){if(1==a)return 1;if(0==a)return 0;var b=0.48-a/1.04,e=Math.sqrt(0.1734+b*b);a=e-b;a=Math.pow(Math.abs(a), 1/3)*(0&gt;a?-1:1);b=-e-b;b=Math.pow(Math.abs(b),1/3)*(0&gt;b?-1:1);a=a+b+0.5;return 3*(1-a)*a*a+a*a*a};e.backin=function(a){return 1==a?1:a*a*(2.70158*a-1.70158)};e.backout=function(a){if(0==a)return 0;a-=1;return a*a*(2.70158*a+1.70158)+1};e.elastic=function(a){return a==!!a?a:Math.pow(2,-10*a)*Math.sin(2*(a-0.075)*Math.PI/0.3)+1};e.bounce=function(a){a&lt;1/2.75?a*=7.5625*a:a&lt;2/2.75?(a-=1.5/2.75,a=7.5625*a*a+0.75):a&lt;2.5/2.75?(a-=2.25/2.75,a=7.5625*a*a+0.9375):(a-=2.625/2.75,a=7.5625*a*a+0.984375);return a}; return N.mina=e}("undefined"==typeof k?function(){}:k),C=function(){function a(c,t){if(c){if(c.tagName)return x(c);if(y(c,"array")&amp;&amp;a.set)return a.set.apply(a,c);if(c instanceof e)return c;if(null==t)return c=G.doc.querySelector(c),x(c)}return new s(null==c?"100%":c,null==t?"100%":t)}function v(c,a){if(a){"#text"==c&amp;&amp;(c=G.doc.createTextNode(a.text||""));"string"==typeof c&amp;&amp;(c=v(c));if("string"==typeof a)return"xlink:"==a.substring(0,6)?c.getAttributeNS(m,a.substring(6)):"xml:"==a.substring(0,4)?c.getAttributeNS(la, a.substring(4)):c.getAttribute(a);for(var da in a)if(a[h](da)){var b=J(a[da]);b?"xlink:"==da.substring(0,6)?c.setAttributeNS(m,da.substring(6),b):"xml:"==da.substring(0,4)?c.setAttributeNS(la,da.substring(4),b):c.setAttribute(da,b):c.removeAttribute(da)}}else c=G.doc.createElementNS(la,c);return c}function y(c,a){a=J.prototype.toLowerCase.call(a);return"finite"==a?isFinite(c):"array"==a&amp;&amp;(c instanceof Array||Array.isArray&amp;&amp;Array.isArray(c))?!0:"null"==a&amp;&amp;null===c||a==typeof c&amp;&amp;null!==c||"object"== a&amp;&amp;c===Object(c)||$.call(c).slice(8,-1).toLowerCase()==a}function M(c){if("function"==typeof c||Object(c)!==c)return c;var a=new c.constructor,b;for(b in c)c[h](b)&amp;&amp;(a[b]=M(c[b]));return a}function A(c,a,b){function m(){var e=Array.prototype.slice.call(arguments,0),f=e.join("\u2400"),d=m.cache=m.cache||{},l=m.count=m.count||[];if(d[h](f)){a:for(var e=l,l=f,B=0,H=e.length;B&lt;H;B++)if(e[B]===l){e.push(e.splice(B,1)[0]);break a}return b?b(d[f]):d[f]}1E3&lt;=l.length&amp;&amp;delete d[l.shift()];l.push(f);d[f]=c.apply(a, e);return b?b(d[f]):d[f]}return m}function w(c,a,b,m,e,f){return null==e?(c-=b,a-=m,c||a?(180*I.atan2(-a,-c)/C+540)%360:0):w(c,a,e,f)-w(b,m,e,f)}function z(c){return c%360*C/180}function d(c){var a=[];c=c.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g,function(c,b,m){m=m.split(/\s*,\s*|\s+/);"rotate"==b&amp;&amp;1==m.length&amp;&amp;m.push(0,0);"scale"==b&amp;&amp;(2&lt;m.length?m=m.slice(0,2):2==m.length&amp;&amp;m.push(0,0),1==m.length&amp;&amp;m.push(m[0],0,0));"skewX"==b?a.push(["m",1,0,I.tan(z(m[0])),1,0,0]):"skewY"==b?a.push(["m",1,I.tan(z(m[0])), 0,1,0,0]):a.push([b.charAt(0)].concat(m));return c});return a}function f(c,t){var b=O(c),m=new a.Matrix;if(b)for(var e=0,f=b.length;e&lt;f;e++){var h=b[e],d=h.length,B=J(h[0]).toLowerCase(),H=h[0]!=B,l=H?m.invert():0,E;"t"==B&amp;&amp;2==d?m.translate(h[1],0):"t"==B&amp;&amp;3==d?H?(d=l.x(0,0),B=l.y(0,0),H=l.x(h[1],h[2]),l=l.y(h[1],h[2]),m.translate(H-d,l-B)):m.translate(h[1],h[2]):"r"==B?2==d?(E=E||t,m.rotate(h[1],E.x+E.width/2,E.y+E.height/2)):4==d&amp;&amp;(H?(H=l.x(h[2],h[3]),l=l.y(h[2],h[3]),m.rotate(h[1],H,l)):m.rotate(h[1], h[2],h[3])):"s"==B?2==d||3==d?(E=E||t,m.scale(h[1],h[d-1],E.x+E.width/2,E.y+E.height/2)):4==d?H?(H=l.x(h[2],h[3]),l=l.y(h[2],h[3]),m.scale(h[1],h[1],H,l)):m.scale(h[1],h[1],h[2],h[3]):5==d&amp;&amp;(H?(H=l.x(h[3],h[4]),l=l.y(h[3],h[4]),m.scale(h[1],h[2],H,l)):m.scale(h[1],h[2],h[3],h[4])):"m"==B&amp;&amp;7==d&amp;&amp;m.add(h[1],h[2],h[3],h[4],h[5],h[6])}return m}function n(c,t){if(null==t){var m=!0;t="linearGradient"==c.type||"radialGradient"==c.type?c.node.getAttribute("gradientTransform"):"pattern"==c.type?c.node.getAttribute("patternTransform"): c.node.getAttribute("transform");if(!t)return new a.Matrix;t=d(t)}else t=a._.rgTransform.test(t)?J(t).replace(/\.{3}|\u2026/g,c._.transform||aa):d(t),y(t,"array")&amp;&amp;(t=a.path?a.path.toString.call(t):J(t)),c._.transform=t;var b=f(t,c.getBBox(1));if(m)return b;c.matrix=b}function u(c){c=c.node.ownerSVGElement&amp;&amp;x(c.node.ownerSVGElement)||c.node.parentNode&amp;&amp;x(c.node.parentNode)||a.select("svg")||a(0,0);var t=c.select("defs"),t=null==t?!1:t.node;t||(t=r("defs",c.node).node);return t}function p(c){return c.node.ownerSVGElement&amp;&amp; x(c.node.ownerSVGElement)||a.select("svg")}function b(c,a,m){function b(c){if(null==c)return aa;if(c==+c)return c;v(B,{width:c});try{return B.getBBox().width}catch(a){return 0}}function h(c){if(null==c)return aa;if(c==+c)return c;v(B,{height:c});try{return B.getBBox().height}catch(a){return 0}}function e(b,B){null==a?d[b]=B(c.attr(b)||0):b==a&amp;&amp;(d=B(null==m?c.attr(b)||0:m))}var f=p(c).node,d={},B=f.querySelector(".svg---mgr");B||(B=v("rect"),v(B,{x:-9E9,y:-9E9,width:10,height:10,"class":"svg---mgr", fill:"none"}),f.appendChild(B));switch(c.type){case "rect":e("rx",b),e("ry",h);case "image":e("width",b),e("height",h);case "text":e("x",b);e("y",h);break;case "circle":e("cx",b);e("cy",h);e("r",b);break;case "ellipse":e("cx",b);e("cy",h);e("rx",b);e("ry",h);break;case "line":e("x1",b);e("x2",b);e("y1",h);e("y2",h);break;case "marker":e("refX",b);e("markerWidth",b);e("refY",h);e("markerHeight",h);break;case "radialGradient":e("fx",b);e("fy",h);break;case "tspan":e("dx",b);e("dy",h);break;default:e(a, b)}f.removeChild(B);return d}function q(c){y(c,"array")||(c=Array.prototype.slice.call(arguments,0));for(var a=0,b=0,m=this.node;this[a];)delete this[a++];for(a=0;a&lt;c.length;a++)"set"==c[a].type?c[a].forEach(function(c){m.appendChild(c.node)}):m.appendChild(c[a].node);for(var h=m.childNodes,a=0;a&lt;h.length;a++)this[b++]=x(h[a]);return this}function e(c){if(c.snap in E)return E[c.snap];var a=this.id=V(),b;try{b=c.ownerSVGElement}catch(m){}this.node=c;b&amp;&amp;(this.paper=new s(b));this.type=c.tagName;this.anims= {};this._={transform:[]};c.snap=a;E[a]=this;"g"==this.type&amp;&amp;(this.add=q);if(this.type in{g:1,mask:1,pattern:1})for(var e in s.prototype)s.prototype[h](e)&amp;&amp;(this[e]=s.prototype[e])}function l(c){this.node=c}function r(c,a){var b=v(c);a.appendChild(b);return x(b)}function s(c,a){var b,m,f,d=s.prototype;if(c&amp;&amp;"svg"==c.tagName){if(c.snap in E)return E[c.snap];var l=c.ownerDocument;b=new e(c);m=c.getElementsByTagName("desc")[0];f=c.getElementsByTagName("defs")[0];m||(m=v("desc"),m.appendChild(l.createTextNode("Created with Snap")), b.node.appendChild(m));f||(f=v("defs"),b.node.appendChild(f));b.defs=f;for(var ca in d)d[h](ca)&amp;&amp;(b[ca]=d[ca]);b.paper=b.root=b}else b=r("svg",G.doc.body),v(b.node,{height:a,version:1.1,width:c,xmlns:la});return b}function x(c){return!c||c instanceof e||c instanceof l?c:c.tagName&amp;&amp;"svg"==c.tagName.toLowerCase()?new s(c):c.tagName&amp;&amp;"object"==c.tagName.toLowerCase()&amp;&amp;"image/svg+xml"==c.type?new s(c.contentDocument.getElementsByTagName("svg")[0]):new e(c)}a.version="0.3.0";a.toString=function(){return"Snap v"+ this.version};a._={};var G={win:N,doc:N.document};a._.glob=G;var h="hasOwnProperty",J=String,K=parseFloat,U=parseInt,I=Math,P=I.max,Q=I.min,Y=I.abs,C=I.PI,aa="",$=Object.prototype.toString,F=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i;a._.separator= RegExp("[,\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]+");var S=RegExp("[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*"),X={hs:1,rg:1},W=RegExp("([a-z])[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)", "ig"),ma=RegExp("([rstm])[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)","ig"),Z=RegExp("(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*", "ig"),na=0,ba="S"+(+new Date).toString(36),V=function(){return ba+(na++).toString(36)},m="http://www.w3.org/1999/xlink",la="http://www.w3.org/2000/svg",E={},ca=a.url=function(c){return"url('#"+c+"')"};a._.$=v;a._.id=V;a.format=function(){var c=/\{([^\}]+)\}/g,a=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,b=function(c,b,m){var h=m;b.replace(a,function(c,a,b,m,t){a=a||m;h&amp;&amp;(a in h&amp;&amp;(h=h[a]),"function"==typeof h&amp;&amp;t&amp;&amp;(h=h()))});return h=(null==h||h==m?c:h)+""};return function(a,m){return J(a).replace(c, function(c,a){return b(c,a,m)})}}();a._.clone=M;a._.cacher=A;a.rad=z;a.deg=function(c){return 180*c/C%360};a.angle=w;a.is=y;a.snapTo=function(c,a,b){b=y(b,"finite")?b:10;if(y(c,"array"))for(var m=c.length;m--;){if(Y(c[m]-a)&lt;=b)return c[m]}else{c=+c;m=a%c;if(m&lt;b)return a-m;if(m&gt;c-b)return a-m+c}return a};a.getRGB=A(function(c){if(!c||(c=J(c)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:ka};if("none"==c)return{r:-1,g:-1,b:-1,hex:"none",toString:ka};!X[h](c.toLowerCase().substring(0, 2))&amp;&amp;"#"!=c.charAt()&amp;&amp;(c=T(c));if(!c)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:ka};var b,m,e,f,d;if(c=c.match(F)){c[2]&amp;&amp;(e=U(c[2].substring(5),16),m=U(c[2].substring(3,5),16),b=U(c[2].substring(1,3),16));c[3]&amp;&amp;(e=U((d=c[3].charAt(3))+d,16),m=U((d=c[3].charAt(2))+d,16),b=U((d=c[3].charAt(1))+d,16));c[4]&amp;&amp;(d=c[4].split(S),b=K(d[0]),"%"==d[0].slice(-1)&amp;&amp;(b*=2.55),m=K(d[1]),"%"==d[1].slice(-1)&amp;&amp;(m*=2.55),e=K(d[2]),"%"==d[2].slice(-1)&amp;&amp;(e*=2.55),"rgba"==c[1].toLowerCase().slice(0,4)&amp;&amp;(f=K(d[3])), d[3]&amp;&amp;"%"==d[3].slice(-1)&amp;&amp;(f/=100));if(c[5])return d=c[5].split(S),b=K(d[0]),"%"==d[0].slice(-1)&amp;&amp;(b/=100),m=K(d[1]),"%"==d[1].slice(-1)&amp;&amp;(m/=100),e=K(d[2]),"%"==d[2].slice(-1)&amp;&amp;(e/=100),"deg"!=d[0].slice(-3)&amp;&amp;"\u00b0"!=d[0].slice(-1)||(b/=360),"hsba"==c[1].toLowerCase().slice(0,4)&amp;&amp;(f=K(d[3])),d[3]&amp;&amp;"%"==d[3].slice(-1)&amp;&amp;(f/=100),a.hsb2rgb(b,m,e,f);if(c[6])return d=c[6].split(S),b=K(d[0]),"%"==d[0].slice(-1)&amp;&amp;(b/=100),m=K(d[1]),"%"==d[1].slice(-1)&amp;&amp;(m/=100),e=K(d[2]),"%"==d[2].slice(-1)&amp;&amp;(e/=100), "deg"!=d[0].slice(-3)&amp;&amp;"\u00b0"!=d[0].slice(-1)||(b/=360),"hsla"==c[1].toLowerCase().slice(0,4)&amp;&amp;(f=K(d[3])),d[3]&amp;&amp;"%"==d[3].slice(-1)&amp;&amp;(f/=100),a.hsl2rgb(b,m,e,f);b=Q(I.round(b),255);m=Q(I.round(m),255);e=Q(I.round(e),255);f=Q(P(f,0),1);c={r:b,g:m,b:e,toString:ka};c.hex="#"+(16777216|e|m&lt;&lt;8|b&lt;&lt;16).toString(16).slice(1);c.opacity=y(f,"finite")?f:1;return c}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:ka}},a);a.hsb=A(function(c,b,m){return a.hsb2rgb(c,b,m).hex});a.hsl=A(function(c,b,m){return a.hsl2rgb(c, b,m).hex});a.rgb=A(function(c,a,b,m){if(y(m,"finite")){var e=I.round;return"rgba("+[e(c),e(a),e(b),+m.toFixed(2)]+")"}return"#"+(16777216|b|a&lt;&lt;8|c&lt;&lt;16).toString(16).slice(1)});var T=function(c){var a=G.doc.getElementsByTagName("head")[0]||G.doc.getElementsByTagName("svg")[0];T=A(function(c){if("red"==c.toLowerCase())return"rgb(255, 0, 0)";a.style.color="rgb(255, 0, 0)";a.style.color=c;c=G.doc.defaultView.getComputedStyle(a,aa).getPropertyValue("color");return"rgb(255, 0, 0)"==c?null:c});return T(c)}, qa=function(){return"hsb("+[this.h,this.s,this.b]+")"},ra=function(){return"hsl("+[this.h,this.s,this.l]+")"},ka=function(){return 1==this.opacity||null==this.opacity?this.hex:"rgba("+[this.r,this.g,this.b,this.opacity]+")"},D=function(c,b,m){null==b&amp;&amp;y(c,"object")&amp;&amp;"r"in c&amp;&amp;"g"in c&amp;&amp;"b"in c&amp;&amp;(m=c.b,b=c.g,c=c.r);null==b&amp;&amp;y(c,string)&amp;&amp;(m=a.getRGB(c),c=m.r,b=m.g,m=m.b);if(1&lt;c||1&lt;b||1&lt;m)c/=255,b/=255,m/=255;return[c,b,m]},oa=function(c,b,m,e){c=I.round(255*c);b=I.round(255*b);m=I.round(255*m);c={r:c, g:b,b:m,opacity:y(e,"finite")?e:1,hex:a.rgb(c,b,m),toString:ka};y(e,"finite")&amp;&amp;(c.opacity=e);return c};a.color=function(c){var b;y(c,"object")&amp;&amp;"h"in c&amp;&amp;"s"in c&amp;&amp;"b"in c?(b=a.hsb2rgb(c),c.r=b.r,c.g=b.g,c.b=b.b,c.opacity=1,c.hex=b.hex):y(c,"object")&amp;&amp;"h"in c&amp;&amp;"s"in c&amp;&amp;"l"in c?(b=a.hsl2rgb(c),c.r=b.r,c.g=b.g,c.b=b.b,c.opacity=1,c.hex=b.hex):(y(c,"string")&amp;&amp;(c=a.getRGB(c)),y(c,"object")&amp;&amp;"r"in c&amp;&amp;"g"in c&amp;&amp;"b"in c&amp;&amp;!("error"in c)?(b=a.rgb2hsl(c),c.h=b.h,c.s=b.s,c.l=b.l,b=a.rgb2hsb(c),c.v=b.b):(c={hex:"none"}, c.r=c.g=c.b=c.h=c.s=c.v=c.l=-1,c.error=1));c.toString=ka;return c};a.hsb2rgb=function(c,a,b,m){y(c,"object")&amp;&amp;"h"in c&amp;&amp;"s"in c&amp;&amp;"b"in c&amp;&amp;(b=c.b,a=c.s,c=c.h,m=c.o);var e,h,d;c=360*c%360/60;d=b*a;a=d*(1-Y(c%2-1));b=e=h=b-d;c=~~c;b+=[d,a,0,0,a,d][c];e+=[a,d,d,a,0,0][c];h+=[0,0,a,d,d,a][c];return oa(b,e,h,m)};a.hsl2rgb=function(c,a,b,m){y(c,"object")&amp;&amp;"h"in c&amp;&amp;"s"in c&amp;&amp;"l"in c&amp;&amp;(b=c.l,a=c.s,c=c.h);if(1&lt;c||1&lt;a||1&lt;b)c/=360,a/=100,b/=100;var e,h,d;c=360*c%360/60;d=2*a*(0.5&gt;b?b:1-b);a=d*(1-Y(c%2-1));b=e= h=b-d/2;c=~~c;b+=[d,a,0,0,a,d][c];e+=[a,d,d,a,0,0][c];h+=[0,0,a,d,d,a][c];return oa(b,e,h,m)};a.rgb2hsb=function(c,a,b){b=D(c,a,b);c=b[0];a=b[1];b=b[2];var m,e;m=P(c,a,b);e=m-Q(c,a,b);c=((0==e?0:m==c?(a-b)/e:m==a?(b-c)/e+2:(c-a)/e+4)+360)%6*60/360;return{h:c,s:0==e?0:e/m,b:m,toString:qa}};a.rgb2hsl=function(c,a,b){b=D(c,a,b);c=b[0];a=b[1];b=b[2];var m,e,h;m=P(c,a,b);e=Q(c,a,b);h=m-e;c=((0==h?0:m==c?(a-b)/h:m==a?(b-c)/h+2:(c-a)/h+4)+360)%6*60/360;m=(m+e)/2;return{h:c,s:0==h?0:0.5&gt;m?h/(2*m):h/(2-2* m),l:m,toString:ra}};a.parsePathString=function(c){if(!c)return null;var b=a.path(c);if(b.arr)return a.path.clone(b.arr);var m={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},e=[];y(c,"array")&amp;&amp;y(c[0],"array")&amp;&amp;(e=a.path.clone(c));e.length||J(c).replace(W,function(c,a,b){var h=[];c=a.toLowerCase();b.replace(Z,function(c,a){a&amp;&amp;h.push(+a)});"m"==c&amp;&amp;2&lt;h.length&amp;&amp;(e.push([a].concat(h.splice(0,2))),c="l",a="m"==a?"l":"L");"o"==c&amp;&amp;1==h.length&amp;&amp;e.push([a,h[0] ]);if("r"==c)e.push([a].concat(h));else for(;h.length&gt;= m[c]&amp;&amp;(e.push([a].concat(h.splice(0,m[c]))),m[c]););});e.toString=a.path.toString;b.arr=a.path.clone(e);return e};var O=a.parseTransformString=function(c){if(!c)return null;var b=[];y(c,"array")&amp;&amp;y(c[0],"array")&amp;&amp;(b=a.path.clone(c));b.length||J(c).replace(ma,function(c,a,m){var e=[];a.toLowerCase();m.replace(Z,function(c,a){a&amp;&amp;e.push(+a)});b.push([a].concat(e))});b.toString=a.path.toString;return b};a._.svgTransform2string=d;a._.rgTransform=RegExp("^[a-z][\t\n\x0B\f\r \u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*-?\\.?\\d", "i");a._.transform2matrix=f;a._unit2px=b;a._.getSomeDefs=u;a._.getSomeSVG=p;a.select=function(c){return x(G.doc.querySelector(c))};a.selectAll=function(c){c=G.doc.querySelectorAll(c);for(var b=(a.set||Array)(),m=0;m&lt;c.length;m++)b.push(x(c[m]));return b};setInterval(function(){for(var c in E)if(E[h](c)){var a=E[c],b=a.node;("svg"!=a.type&amp;&amp;!b.ownerSVGElement||"svg"==a.type&amp;&amp;(!b.parentNode||"ownerSVGElement"in b.parentNode&amp;&amp;!b.ownerSVGElement))&amp;&amp;delete E[c]}},1E4);(function(c){function m(c){function a(c, b){var m=v(c.node,b);(m=(m=m&amp;&amp;m.match(d))&amp;&amp;m[2])&amp;&amp;"#"==m.charAt()&amp;&amp;(m=m.substring(1))&amp;&amp;(f[m]=(f[m]||[]).concat(function(a){var m={};m[b]=ca(a);v(c.node,m)}))}function b(c){var a=v(c.node,"xlink:href");a&amp;&amp;"#"==a.charAt()&amp;&amp;(a=a.substring(1))&amp;&amp;(f[a]=(f[a]||[]).concat(function(a){c.attr("xlink:href","#"+a)}))}var e=c.selectAll("*"),h,d=/^\s*url\(("|'|)(.*)\1\)\s*$/;c=[];for(var f={},l=0,E=e.length;l&lt;E;l++){h=e[l];a(h,"fill");a(h,"stroke");a(h,"filter");a(h,"mask");a(h,"clip-path");b(h);var t=v(h.node, "id");t&amp;&amp;(v(h.node,{id:h.id}),c.push({old:t,id:h.id}))}l=0;for(E=c.length;l&lt;E;l++)if(e=f[c[l].old])for(h=0,t=e.length;h&lt;t;h++)e[h](c[l].id)}function e(c,a,b){return function(m){m=m.slice(c,a);1==m.length&amp;&amp;(m=m[0]);return b?b(m):m}}function d(c){return function(){var a=c?"&lt;"+this.type:"",b=this.node.attributes,m=this.node.childNodes;if(c)for(var e=0,h=b.length;e&lt;h;e++)a+=" "+b[e].name+'="'+b[e].value.replace(/"/g,'\\"')+'"';if(m.length){c&amp;&amp;(a+="&gt;");e=0;for(h=m.length;e&lt;h;e++)3==m[e].nodeType?a+=m[e].nodeValue: 1==m[e].nodeType&amp;&amp;(a+=x(m[e]).toString());c&amp;&amp;(a+="&lt;/"+this.type+"&gt;")}else c&amp;&amp;(a+="/&gt;");return a}}c.attr=function(c,a){if(!c)return this;if(y(c,"string"))if(1&lt;arguments.length){var b={};b[c]=a;c=b}else return k("snap.util.getattr."+c,this).firstDefined();for(var m in c)c[h](m)&amp;&amp;k("snap.util.attr."+m,this,c[m]);return this};c.getBBox=function(c){if(!a.Matrix||!a.path)return this.node.getBBox();var b=this,m=new a.Matrix;if(b.removed)return a._.box();for(;"use"==b.type;)if(c||(m=m.add(b.transform().localMatrix.translate(b.attr("x")|| 0,b.attr("y")||0))),b.original)b=b.original;else var e=b.attr("xlink:href"),b=b.original=b.node.ownerDocument.getElementById(e.substring(e.indexOf("#")+1));var e=b._,h=a.path.get[b.type]||a.path.get.deflt;try{if(c)return e.bboxwt=h?a.path.getBBox(b.realPath=h(b)):a._.box(b.node.getBBox()),a._.box(e.bboxwt);b.realPath=h(b);b.matrix=b.transform().localMatrix;e.bbox=a.path.getBBox(a.path.map(b.realPath,m.add(b.matrix)));return a._.box(e.bbox)}catch(d){return a._.box()}};var f=function(){return this.string}; c.transform=function(c){var b=this._;if(null==c){var m=this;c=new a.Matrix(this.node.getCTM());for(var e=n(this),h=[e],d=new a.Matrix,l=e.toTransformString(),b=J(e)==J(this.matrix)?J(b.transform):l;"svg"!=m.type&amp;&amp;(m=m.parent());)h.push(n(m));for(m=h.length;m--;)d.add(h[m]);return{string:b,globalMatrix:c,totalMatrix:d,localMatrix:e,diffMatrix:c.clone().add(e.invert()),global:c.toTransformString(),total:d.toTransformString(),local:l,toString:f}}c instanceof a.Matrix?this.matrix=c:n(this,c);this.node&amp;&amp; ("linearGradient"==this.type||"radialGradient"==this.type?v(this.node,{gradientTransform:this.matrix}):"pattern"==this.type?v(this.node,{patternTransform:this.matrix}):v(this.node,{transform:this.matrix}));return this};c.parent=function(){return x(this.node.parentNode)};c.append=c.add=function(c){if(c){if("set"==c.type){var a=this;c.forEach(function(c){a.add(c)});return this}c=x(c);this.node.appendChild(c.node);c.paper=this.paper}return this};c.appendTo=function(c){c&amp;&amp;(c=x(c),c.append(this));return this}; c.prepend=function(c){if(c){if("set"==c.type){var a=this,b;c.forEach(function(c){b?b.after(c):a.prepend(c);b=c});return this}c=x(c);var m=c.parent();this.node.insertBefore(c.node,this.node.firstChild);this.add&amp;&amp;this.add();c.paper=this.paper;this.parent()&amp;&amp;this.parent().add();m&amp;&amp;m.add()}return this};c.prependTo=function(c){c=x(c);c.prepend(this);return this};c.before=function(c){if("set"==c.type){var a=this;c.forEach(function(c){var b=c.parent();a.node.parentNode.insertBefore(c.node,a.node);b&amp;&amp;b.add()}); this.parent().add();return this}c=x(c);var b=c.parent();this.node.parentNode.insertBefore(c.node,this.node);this.parent()&amp;&amp;this.parent().add();b&amp;&amp;b.add();c.paper=this.paper;return this};c.after=function(c){c=x(c);var a=c.parent();this.node.nextSibling?this.node.parentNode.insertBefore(c.node,this.node.nextSibling):this.node.parentNode.appendChild(c.node);this.parent()&amp;&amp;this.parent().add();a&amp;&amp;a.add();c.paper=this.paper;return this};c.insertBefore=function(c){c=x(c);var a=this.parent();c.node.parentNode.insertBefore(this.node, c.node);this.paper=c.paper;a&amp;&amp;a.add();c.parent()&amp;&amp;c.parent().add();return this};c.insertAfter=function(c){c=x(c);var a=this.parent();c.node.parentNode.insertBefore(this.node,c.node.nextSibling);this.paper=c.paper;a&amp;&amp;a.add();c.parent()&amp;&amp;c.parent().add();return this};c.remove=function(){var c=this.parent();this.node.parentNode&amp;&amp;this.node.parentNode.removeChild(this.node);delete this.paper;this.removed=!0;c&amp;&amp;c.add();return this};c.select=function(c){return x(this.node.querySelector(c))};c.selectAll= function(c){c=this.node.querySelectorAll(c);for(var b=(a.set||Array)(),m=0;m&lt;c.length;m++)b.push(x(c[m]));return b};c.asPX=function(c,a){null==a&amp;&amp;(a=this.attr(c));return+b(this,c,a)};c.use=function(){var c,a=this.node.id;a||(a=this.id,v(this.node,{id:a}));c="linearGradient"==this.type||"radialGradient"==this.type||"pattern"==this.type?r(this.type,this.node.parentNode):r("use",this.node.parentNode);v(c.node,{"xlink:href":"#"+a});c.original=this;return c};var l=/\S+/g;c.addClass=function(c){var a=(c|| "").match(l)||[];c=this.node;var b=c.className.baseVal,m=b.match(l)||[],e,h,d;if(a.length){for(e=0;d=a[e++];)h=m.indexOf(d),~h||m.push(d);a=m.join(" ");b!=a&amp;&amp;(c.className.baseVal=a)}return this};c.removeClass=function(c){var a=(c||"").match(l)||[];c=this.node;var b=c.className.baseVal,m=b.match(l)||[],e,h;if(m.length){for(e=0;h=a[e++];)h=m.indexOf(h),~h&amp;&amp;m.splice(h,1);a=m.join(" ");b!=a&amp;&amp;(c.className.baseVal=a)}return this};c.hasClass=function(c){return!!~(this.node.className.baseVal.match(l)||[]).indexOf(c)}; c.toggleClass=function(c,a){if(null!=a)return a?this.addClass(c):this.removeClass(c);var b=(c||"").match(l)||[],m=this.node,e=m.className.baseVal,h=e.match(l)||[],d,f,E;for(d=0;E=b[d++];)f=h.indexOf(E),~f?h.splice(f,1):h.push(E);b=h.join(" ");e!=b&amp;&amp;(m.className.baseVal=b);return this};c.clone=function(){var c=x(this.node.cloneNode(!0));v(c.node,"id")&amp;&amp;v(c.node,{id:c.id});m(c);c.insertAfter(this);return c};c.toDefs=function(){u(this).appendChild(this.node);return this};c.pattern=c.toPattern=function(c, a,b,m){var e=r("pattern",u(this));null==c&amp;&amp;(c=this.getBBox());y(c,"object")&amp;&amp;"x"in c&amp;&amp;(a=c.y,b=c.width,m=c.height,c=c.x);v(e.node,{x:c,y:a,width:b,height:m,patternUnits:"userSpaceOnUse",id:e.id,viewBox:[c,a,b,m].join(" ")});e.node.appendChild(this.node);return e};c.marker=function(c,a,b,m,e,h){var d=r("marker",u(this));null==c&amp;&amp;(c=this.getBBox());y(c,"object")&amp;&amp;"x"in c&amp;&amp;(a=c.y,b=c.width,m=c.height,e=c.refX||c.cx,h=c.refY||c.cy,c=c.x);v(d.node,{viewBox:[c,a,b,m].join(" "),markerWidth:b,markerHeight:m, orient:"auto",refX:e||0,refY:h||0,id:d.id});d.node.appendChild(this.node);return d};var E=function(c,a,b,m){"function"!=typeof b||b.length||(m=b,b=L.linear);this.attr=c;this.dur=a;b&amp;&amp;(this.easing=b);m&amp;&amp;(this.callback=m)};a._.Animation=E;a.animation=function(c,a,b,m){return new E(c,a,b,m)};c.inAnim=function(){var c=[],a;for(a in this.anims)this.anims[h](a)&amp;&amp;function(a){c.push({anim:new E(a._attrs,a.dur,a.easing,a._callback),mina:a,curStatus:a.status(),status:function(c){return a.status(c)},stop:function(){a.stop()}})}(this.anims[a]); return c};a.animate=function(c,a,b,m,e,h){"function"!=typeof e||e.length||(h=e,e=L.linear);var d=L.time();c=L(c,a,d,d+m,L.time,b,e);h&amp;&amp;k.once("mina.finish."+c.id,h);return c};c.stop=function(){for(var c=this.inAnim(),a=0,b=c.length;a&lt;b;a++)c[a].stop();return this};c.animate=function(c,a,b,m){"function"!=typeof b||b.length||(m=b,b=L.linear);c instanceof E&amp;&amp;(m=c.callback,b=c.easing,a=b.dur,c=c.attr);var d=[],f=[],l={},t,ca,n,T=this,q;for(q in c)if(c[h](q)){T.equal?(n=T.equal(q,J(c[q])),t=n.from,ca= n.to,n=n.f):(t=+T.attr(q),ca=+c[q]);var la=y(t,"array")?t.length:1;l[q]=e(d.length,d.length+la,n);d=d.concat(t);f=f.concat(ca)}t=L.time();var p=L(d,f,t,t+a,L.time,function(c){var a={},b;for(b in l)l[h](b)&amp;&amp;(a[b]=l[b](c));T.attr(a)},b);T.anims[p.id]=p;p._attrs=c;p._callback=m;k("snap.animcreated."+T.id,p);k.once("mina.finish."+p.id,function(){delete T.anims[p.id];m&amp;&amp;m.call(T)});k.once("mina.stop."+p.id,function(){delete T.anims[p.id]});return T};var T={};c.data=function(c,b){var m=T[this.id]=T[this.id]|| {};if(0==arguments.length)return k("snap.data.get."+this.id,this,m,null),m;if(1==arguments.length){if(a.is(c,"object")){for(var e in c)c[h](e)&amp;&amp;this.data(e,c[e]);return this}k("snap.data.get."+this.id,this,m[c],c);return m[c]}m[c]=b;k("snap.data.set."+this.id,this,b,c);return this};c.removeData=function(c){null==c?T[this.id]={}:T[this.id]&amp;&amp;delete T[this.id][c];return this};c.outerSVG=c.toString=d(1);c.innerSVG=d()})(e.prototype);a.parse=function(c){var a=G.doc.createDocumentFragment(),b=!0,m=G.doc.createElement("div"); c=J(c);c.match(/^\s*&lt;\s*svg(?:\s|&gt;)/)||(c="&lt;svg&gt;"+c+"&lt;/svg&gt;",b=!1);m.innerHTML=c;if(c=m.getElementsByTagName("svg")[0])if(b)a=c;else for(;c.firstChild;)a.appendChild(c.firstChild);m.innerHTML=aa;return new l(a)};l.prototype.select=e.prototype.select;l.prototype.selectAll=e.prototype.selectAll;a.fragment=function(){for(var c=Array.prototype.slice.call(arguments,0),b=G.doc.createDocumentFragment(),m=0,e=c.length;m&lt;e;m++){var h=c[m];h.node&amp;&amp;h.node.nodeType&amp;&amp;b.appendChild(h.node);h.nodeType&amp;&amp;b.appendChild(h); "string"==typeof h&amp;&amp;b.appendChild(a.parse(h).node)}return new l(b)};a._.make=r;a._.wrap=x;s.prototype.el=function(c,a){var b=r(c,this.node);a&amp;&amp;b.attr(a);return b};k.on("snap.util.getattr",function(){var c=k.nt(),c=c.substring(c.lastIndexOf(".")+1),a=c.replace(/[A-Z]/g,function(c){return"-"+c.toLowerCase()});return pa[h](a)?this.node.ownerDocument.defaultView.getComputedStyle(this.node,null).getPropertyValue(a):v(this.node,c)});var pa={"alignment-baseline":0,"baseline-shift":0,clip:0,"clip-path":0, "clip-rule":0,color:0,"color-interpolation":0,"color-interpolation-filters":0,"color-profile":0,"color-rendering":0,cursor:0,direction:0,display:0,"dominant-baseline":0,"enable-background":0,fill:0,"fill-opacity":0,"fill-rule":0,filter:0,"flood-color":0,"flood-opacity":0,font:0,"font-family":0,"font-size":0,"font-size-adjust":0,"font-stretch":0,"font-style":0,"font-variant":0,"font-weight":0,"glyph-orientation-horizontal":0,"glyph-orientation-vertical":0,"image-rendering":0,kerning:0,"letter-spacing":0, "lighting-color":0,marker:0,"marker-end":0,"marker-mid":0,"marker-start":0,mask:0,opacity:0,overflow:0,"pointer-events":0,"shape-rendering":0,"stop-color":0,"stop-opacity":0,stroke:0,"stroke-dasharray":0,"stroke-dashoffset":0,"stroke-linecap":0,"stroke-linejoin":0,"stroke-miterlimit":0,"stroke-opacity":0,"stroke-width":0,"text-anchor":0,"text-decoration":0,"text-rendering":0,"unicode-bidi":0,visibility:0,"word-spacing":0,"writing-mode":0};k.on("snap.util.attr",function(c){var a=k.nt(),b={},a=a.substring(a.lastIndexOf(".")+ 1);b[a]=c;var m=a.replace(/-(\w)/gi,function(c,a){return a.toUpperCase()}),a=a.replace(/[A-Z]/g,function(c){return"-"+c.toLowerCase()});pa[h](a)?this.node.style[m]=null==c?aa:c:v(this.node,b)});a.ajax=function(c,a,b,m){var e=new XMLHttpRequest,h=V();if(e){if(y(a,"function"))m=b,b=a,a=null;else if(y(a,"object")){var d=[],f;for(f in a)a.hasOwnProperty(f)&amp;&amp;d.push(encodeURIComponent(f)+"="+encodeURIComponent(a[f]));a=d.join("&amp;")}e.open(a?"POST":"GET",c,!0);a&amp;&amp;(e.setRequestHeader("X-Requested-With","XMLHttpRequest"), e.setRequestHeader("Content-type","application/x-www-form-urlencoded"));b&amp;&amp;(k.once("snap.ajax."+h+".0",b),k.once("snap.ajax."+h+".200",b),k.once("snap.ajax."+h+".304",b));e.onreadystatechange=function(){4==e.readyState&amp;&amp;k("snap.ajax."+h+"."+e.status,m,e)};if(4==e.readyState)return e;e.send(a);return e}};a.load=function(c,b,m){a.ajax(c,function(c){c=a.parse(c.responseText);m?b.call(m,c):b(c)})};a.getElementByPoint=function(c,a){var b,m,e=G.doc.elementFromPoint(c,a);if(G.win.opera&amp;&amp;"svg"==e.tagName){b= e;m=b.getBoundingClientRect();b=b.ownerDocument;var h=b.body,d=b.documentElement;b=m.top+(g.win.pageYOffset||d.scrollTop||h.scrollTop)-(d.clientTop||h.clientTop||0);m=m.left+(g.win.pageXOffset||d.scrollLeft||h.scrollLeft)-(d.clientLeft||h.clientLeft||0);h=e.createSVGRect();h.x=c-m;h.y=a-b;h.width=h.height=1;b=e.getIntersectionList(h,null);b.length&amp;&amp;(e=b[b.length-1])}return e?x(e):null};a.plugin=function(c){c(a,e,s,G,l)};return G.win.Snap=a}();C.plugin(function(a,k,y,M,A){function w(a,d,f,b,q,e){null== d&amp;&amp;"[object SVGMatrix]"==z.call(a)?(this.a=a.a,this.b=a.b,this.c=a.c,this.d=a.d,this.e=a.e,this.f=a.f):null!=a?(this.a=+a,this.b=+d,this.c=+f,this.d=+b,this.e=+q,this.f=+e):(this.a=1,this.c=this.b=0,this.d=1,this.f=this.e=0)}var z=Object.prototype.toString,d=String,f=Math;(function(n){function k(a){return a[0]*a[0]+a[1]*a[1]}function p(a){var d=f.sqrt(k(a));a[0]&amp;&amp;(a[0]/=d);a[1]&amp;&amp;(a[1]/=d)}n.add=function(a,d,e,f,n,p){var k=[[],[],[] ],u=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1] ];d=[[a, e,n],[d,f,p],[0,0,1] ];a&amp;&amp;a instanceof w&amp;&amp;(d=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1] ]);for(a=0;3&gt;a;a++)for(e=0;3&gt;e;e++){for(f=n=0;3&gt;f;f++)n+=u[a][f]*d[f][e];k[a][e]=n}this.a=k[0][0];this.b=k[1][0];this.c=k[0][1];this.d=k[1][1];this.e=k[0][2];this.f=k[1][2];return this};n.invert=function(){var a=this.a*this.d-this.b*this.c;return new w(this.d/a,-this.b/a,-this.c/a,this.a/a,(this.c*this.f-this.d*this.e)/a,(this.b*this.e-this.a*this.f)/a)};n.clone=function(){return new w(this.a,this.b,this.c,this.d,this.e, this.f)};n.translate=function(a,d){return this.add(1,0,0,1,a,d)};n.scale=function(a,d,e,f){null==d&amp;&amp;(d=a);(e||f)&amp;&amp;this.add(1,0,0,1,e,f);this.add(a,0,0,d,0,0);(e||f)&amp;&amp;this.add(1,0,0,1,-e,-f);return this};n.rotate=function(b,d,e){b=a.rad(b);d=d||0;e=e||0;var l=+f.cos(b).toFixed(9);b=+f.sin(b).toFixed(9);this.add(l,b,-b,l,d,e);return this.add(1,0,0,1,-d,-e)};n.x=function(a,d){return a*this.a+d*this.c+this.e};n.y=function(a,d){return a*this.b+d*this.d+this.f};n.get=function(a){return+this[d.fromCharCode(97+ a)].toFixed(4)};n.toString=function(){return"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")"};n.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]};n.determinant=function(){return this.a*this.d-this.b*this.c};n.split=function(){var b={};b.dx=this.e;b.dy=this.f;var d=[[this.a,this.c],[this.b,this.d] ];b.scalex=f.sqrt(k(d[0]));p(d[0]);b.shear=d[0][0]*d[1][0]+d[0][1]*d[1][1];d[1]=[d[1][0]-d[0][0]*b.shear,d[1][1]-d[0][1]*b.shear];b.scaley=f.sqrt(k(d[1])); p(d[1]);b.shear/=b.scaley;0&gt;this.determinant()&amp;&amp;(b.scalex=-b.scalex);var e=-d[0][1],d=d[1][1];0&gt;d?(b.rotate=a.deg(f.acos(d)),0&gt;e&amp;&amp;(b.rotate=360-b.rotate)):b.rotate=a.deg(f.asin(e));b.isSimple=!+b.shear.toFixed(9)&amp;&amp;(b.scalex.toFixed(9)==b.scaley.toFixed(9)||!b.rotate);b.isSuperSimple=!+b.shear.toFixed(9)&amp;&amp;b.scalex.toFixed(9)==b.scaley.toFixed(9)&amp;&amp;!b.rotate;b.noRotation=!+b.shear.toFixed(9)&amp;&amp;!b.rotate;return b};n.toTransformString=function(a){a=a||this.split();if(+a.shear.toFixed(9))return"m"+[this.get(0), this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)];a.scalex=+a.scalex.toFixed(4);a.scaley=+a.scaley.toFixed(4);a.rotate=+a.rotate.toFixed(4);return(a.dx||a.dy?"t"+[+a.dx.toFixed(4),+a.dy.toFixed(4)]:"")+(1!=a.scalex||1!=a.scaley?"s"+[a.scalex,a.scaley,0,0]:"")+(a.rotate?"r"+[+a.rotate.toFixed(4),0,0]:"")}})(w.prototype);a.Matrix=w;a.matrix=function(a,d,f,b,k,e){return new w(a,d,f,b,k,e)}});C.plugin(function(a,v,y,M,A){function w(h){return function(d){k.stop();d instanceof A&amp;&amp;1==d.node.childNodes.length&amp;&amp; ("radialGradient"==d.node.firstChild.tagName||"linearGradient"==d.node.firstChild.tagName||"pattern"==d.node.firstChild.tagName)&amp;&amp;(d=d.node.firstChild,b(this).appendChild(d),d=u(d));if(d instanceof v)if("radialGradient"==d.type||"linearGradient"==d.type||"pattern"==d.type){d.node.id||e(d.node,{id:d.id});var f=l(d.node.id)}else f=d.attr(h);else f=a.color(d),f.error?(f=a(b(this).ownerSVGElement).gradient(d))?(f.node.id||e(f.node,{id:f.id}),f=l(f.node.id)):f=d:f=r(f);d={};d[h]=f;e(this.node,d);this.node.style[h]= x}}function z(a){k.stop();a==+a&amp;&amp;(a+="px");this.node.style.fontSize=a}function d(a){var b=[];a=a.childNodes;for(var e=0,f=a.length;e&lt;f;e++){var l=a[e];3==l.nodeType&amp;&amp;b.push(l.nodeValue);"tspan"==l.tagName&amp;&amp;(1==l.childNodes.length&amp;&amp;3==l.firstChild.nodeType?b.push(l.firstChild.nodeValue):b.push(d(l)))}return b}function f(){k.stop();return this.node.style.fontSize}var n=a._.make,u=a._.wrap,p=a.is,b=a._.getSomeDefs,q=/^url\(#?([^)]+)\)$/,e=a._.$,l=a.url,r=String,s=a._.separator,x="";k.on("snap.util.attr.mask", function(a){if(a instanceof v||a instanceof A){k.stop();a instanceof A&amp;&amp;1==a.node.childNodes.length&amp;&amp;(a=a.node.firstChild,b(this).appendChild(a),a=u(a));if("mask"==a.type)var d=a;else d=n("mask",b(this)),d.node.appendChild(a.node);!d.node.id&amp;&amp;e(d.node,{id:d.id});e(this.node,{mask:l(d.id)})}});(function(a){k.on("snap.util.attr.clip",a);k.on("snap.util.attr.clip-path",a);k.on("snap.util.attr.clipPath",a)})(function(a){if(a instanceof v||a instanceof A){k.stop();if("clipPath"==a.type)var d=a;else d= n("clipPath",b(this)),d.node.appendChild(a.node),!d.node.id&amp;&amp;e(d.node,{id:d.id});e(this.node,{"clip-path":l(d.id)})}});k.on("snap.util.attr.fill",w("fill"));k.on("snap.util.attr.stroke",w("stroke"));var G=/^([lr])(?:\(([^)]*)\))?(.*)$/i;k.on("snap.util.grad.parse",function(a){a=r(a);var b=a.match(G);if(!b)return null;a=b[1];var e=b[2],b=b[3],e=e.split(/\s*,\s*/).map(function(a){return+a==a?+a:a});1==e.length&amp;&amp;0==e[0]&amp;&amp;(e=[]);b=b.split("-");b=b.map(function(a){a=a.split(":");var b={color:a[0]};a[1]&amp;&amp; (b.offset=parseFloat(a[1]));return b});return{type:a,params:e,stops:b}});k.on("snap.util.attr.d",function(b){k.stop();p(b,"array")&amp;&amp;p(b[0],"array")&amp;&amp;(b=a.path.toString.call(b));b=r(b);b.match(/[ruo]/i)&amp;&amp;(b=a.path.toAbsolute(b));e(this.node,{d:b})})(-1);k.on("snap.util.attr.#text",function(a){k.stop();a=r(a);for(a=M.doc.createTextNode(a);this.node.firstChild;)this.node.removeChild(this.node.firstChild);this.node.appendChild(a)})(-1);k.on("snap.util.attr.path",function(a){k.stop();this.attr({d:a})})(-1); k.on("snap.util.attr.class",function(a){k.stop();this.node.className.baseVal=a})(-1);k.on("snap.util.attr.viewBox",function(a){a=p(a,"object")&amp;&amp;"x"in a?[a.x,a.y,a.width,a.height].join(" "):p(a,"array")?a.join(" "):a;e(this.node,{viewBox:a});k.stop()})(-1);k.on("snap.util.attr.transform",function(a){this.transform(a);k.stop()})(-1);k.on("snap.util.attr.r",function(a){"rect"==this.type&amp;&amp;(k.stop(),e(this.node,{rx:a,ry:a}))})(-1);k.on("snap.util.attr.textpath",function(a){k.stop();if("text"==this.type){var d, f;if(!a&amp;&amp;this.textPath){for(a=this.textPath;a.node.firstChild;)this.node.appendChild(a.node.firstChild);a.remove();delete this.textPath}else if(p(a,"string")?(d=b(this),a=u(d.parentNode).path(a),d.appendChild(a.node),d=a.id,a.attr({id:d})):(a=u(a),a instanceof v&amp;&amp;(d=a.attr("id"),d||(d=a.id,a.attr({id:d})))),d)if(a=this.textPath,f=this.node,a)a.attr({"xlink:href":"#"+d});else{for(a=e("textPath",{"xlink:href":"#"+d});f.firstChild;)a.appendChild(f.firstChild);f.appendChild(a);this.textPath=u(a)}}})(-1); k.on("snap.util.attr.text",function(a){if("text"==this.type){for(var b=this.node,d=function(a){var b=e("tspan");if(p(a,"array"))for(var f=0;f&lt;a.length;f++)b.appendChild(d(a[f]));else b.appendChild(M.doc.createTextNode(a));b.normalize&amp;&amp;b.normalize();return b};b.firstChild;)b.removeChild(b.firstChild);for(a=d(a);a.firstChild;)b.appendChild(a.firstChild)}k.stop()})(-1);k.on("snap.util.attr.fontSize",z)(-1);k.on("snap.util.attr.font-size",z)(-1);k.on("snap.util.getattr.transform",function(){k.stop(); return this.transform()})(-1);k.on("snap.util.getattr.textpath",function(){k.stop();return this.textPath})(-1);(function(){function b(d){return function(){k.stop();var b=M.doc.defaultView.getComputedStyle(this.node,null).getPropertyValue("marker-"+d);return"none"==b?b:a(M.doc.getElementById(b.match(q)[1]))}}function d(a){return function(b){k.stop();var d="marker"+a.charAt(0).toUpperCase()+a.substring(1);if(""==b||!b)this.node.style[d]="none";else if("marker"==b.type){var f=b.node.id;f||e(b.node,{id:b.id}); this.node.style[d]=l(f)}}}k.on("snap.util.getattr.marker-end",b("end"))(-1);k.on("snap.util.getattr.markerEnd",b("end"))(-1);k.on("snap.util.getattr.marker-start",b("start"))(-1);k.on("snap.util.getattr.markerStart",b("start"))(-1);k.on("snap.util.getattr.marker-mid",b("mid"))(-1);k.on("snap.util.getattr.markerMid",b("mid"))(-1);k.on("snap.util.attr.marker-end",d("end"))(-1);k.on("snap.util.attr.markerEnd",d("end"))(-1);k.on("snap.util.attr.marker-start",d("start"))(-1);k.on("snap.util.attr.markerStart", d("start"))(-1);k.on("snap.util.attr.marker-mid",d("mid"))(-1);k.on("snap.util.attr.markerMid",d("mid"))(-1)})();k.on("snap.util.getattr.r",function(){if("rect"==this.type&amp;&amp;e(this.node,"rx")==e(this.node,"ry"))return k.stop(),e(this.node,"rx")})(-1);k.on("snap.util.getattr.text",function(){if("text"==this.type||"tspan"==this.type){k.stop();var a=d(this.node);return 1==a.length?a[0]:a}})(-1);k.on("snap.util.getattr.#text",function(){return this.node.textContent})(-1);k.on("snap.util.getattr.viewBox", function(){k.stop();var b=e(this.node,"viewBox");if(b)return b=b.split(s),a._.box(+b[0],+b[1],+b[2],+b[3])})(-1);k.on("snap.util.getattr.points",function(){var a=e(this.node,"points");k.stop();if(a)return a.split(s)})(-1);k.on("snap.util.getattr.path",function(){var a=e(this.node,"d");k.stop();return a})(-1);k.on("snap.util.getattr.class",function(){return this.node.className.baseVal})(-1);k.on("snap.util.getattr.fontSize",f)(-1);k.on("snap.util.getattr.font-size",f)(-1)});C.plugin(function(a,v,y, M,A){function w(a){return a}function z(a){return function(b){return+b.toFixed(3)+a}}var d={"+":function(a,b){return a+b},"-":function(a,b){return a-b},"/":function(a,b){return a/b},"*":function(a,b){return a*b}},f=String,n=/[a-z]+$/i,u=/^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/;k.on("snap.util.attr",function(a){if(a=f(a).match(u)){var b=k.nt(),b=b.substring(b.lastIndexOf(".")+1),q=this.attr(b),e={};k.stop();var l=a[3]||"",r=q.match(n),s=d[a[1] ];r&amp;&amp;r==l?a=s(parseFloat(q),+a[2]):(q=this.asPX(b), a=s(this.asPX(b),this.asPX(b,a[2]+l)));isNaN(q)||isNaN(a)||(e[b]=a,this.attr(e))}})(-10);k.on("snap.util.equal",function(a,b){var q=f(this.attr(a)||""),e=f(b).match(u);if(e){k.stop();var l=e[3]||"",r=q.match(n),s=d[e[1] ];if(r&amp;&amp;r==l)return{from:parseFloat(q),to:s(parseFloat(q),+e[2]),f:z(r)};q=this.asPX(a);return{from:q,to:s(q,this.asPX(a,e[2]+l)),f:w}}})(-10)});C.plugin(function(a,v,y,M,A){var w=y.prototype,z=a.is;w.rect=function(a,d,k,p,b,q){var e;null==q&amp;&amp;(q=b);z(a,"object")&amp;&amp;"[object Object]"== a?e=a:null!=a&amp;&amp;(e={x:a,y:d,width:k,height:p},null!=b&amp;&amp;(e.rx=b,e.ry=q));return this.el("rect",e)};w.circle=function(a,d,k){var p;z(a,"object")&amp;&amp;"[object Object]"==a?p=a:null!=a&amp;&amp;(p={cx:a,cy:d,r:k});return this.el("circle",p)};var d=function(){function a(){this.parentNode.removeChild(this)}return function(d,k){var p=M.doc.createElement("img"),b=M.doc.body;p.style.cssText="position:absolute;left:-9999em;top:-9999em";p.onload=function(){k.call(p);p.onload=p.onerror=null;b.removeChild(p)};p.onerror=a; b.appendChild(p);p.src=d}}();w.image=function(f,n,k,p,b){var q=this.el("image");if(z(f,"object")&amp;&amp;"src"in f)q.attr(f);else if(null!=f){var e={"xlink:href":f,preserveAspectRatio:"none"};null!=n&amp;&amp;null!=k&amp;&amp;(e.x=n,e.y=k);null!=p&amp;&amp;null!=b?(e.width=p,e.height=b):d(f,function(){a._.$(q.node,{width:this.offsetWidth,height:this.offsetHeight})});a._.$(q.node,e)}return q};w.ellipse=function(a,d,k,p){var b;z(a,"object")&amp;&amp;"[object Object]"==a?b=a:null!=a&amp;&amp;(b={cx:a,cy:d,rx:k,ry:p});return this.el("ellipse",b)}; w.path=function(a){var d;z(a,"object")&amp;&amp;!z(a,"array")?d=a:a&amp;&amp;(d={d:a});return this.el("path",d)};w.group=w.g=function(a){var d=this.el("g");1==arguments.length&amp;&amp;a&amp;&amp;!a.type?d.attr(a):arguments.length&amp;&amp;d.add(Array.prototype.slice.call(arguments,0));return d};w.svg=function(a,d,k,p,b,q,e,l){var r={};z(a,"object")&amp;&amp;null==d?r=a:(null!=a&amp;&amp;(r.x=a),null!=d&amp;&amp;(r.y=d),null!=k&amp;&amp;(r.width=k),null!=p&amp;&amp;(r.height=p),null!=b&amp;&amp;null!=q&amp;&amp;null!=e&amp;&amp;null!=l&amp;&amp;(r.viewBox=[b,q,e,l]));return this.el("svg",r)};w.mask=function(a){var d= this.el("mask");1==arguments.length&amp;&amp;a&amp;&amp;!a.type?d.attr(a):arguments.length&amp;&amp;d.add(Array.prototype.slice.call(arguments,0));return d};w.ptrn=function(a,d,k,p,b,q,e,l){if(z(a,"object"))var r=a;else arguments.length?(r={},null!=a&amp;&amp;(r.x=a),null!=d&amp;&amp;(r.y=d),null!=k&amp;&amp;(r.width=k),null!=p&amp;&amp;(r.height=p),null!=b&amp;&amp;null!=q&amp;&amp;null!=e&amp;&amp;null!=l&amp;&amp;(r.viewBox=[b,q,e,l])):r={patternUnits:"userSpaceOnUse"};return this.el("pattern",r)};w.use=function(a){return null!=a?(make("use",this.node),a instanceof v&amp;&amp;(a.attr("id")|| a.attr({id:ID()}),a=a.attr("id")),this.el("use",{"xlink:href":a})):v.prototype.use.call(this)};w.text=function(a,d,k){var p={};z(a,"object")?p=a:null!=a&amp;&amp;(p={x:a,y:d,text:k||""});return this.el("text",p)};w.line=function(a,d,k,p){var b={};z(a,"object")?b=a:null!=a&amp;&amp;(b={x1:a,x2:k,y1:d,y2:p});return this.el("line",b)};w.polyline=function(a){1&lt;arguments.length&amp;&amp;(a=Array.prototype.slice.call(arguments,0));var d={};z(a,"object")&amp;&amp;!z(a,"array")?d=a:null!=a&amp;&amp;(d={points:a});return this.el("polyline",d)}; w.polygon=function(a){1&lt;arguments.length&amp;&amp;(a=Array.prototype.slice.call(arguments,0));var d={};z(a,"object")&amp;&amp;!z(a,"array")?d=a:null!=a&amp;&amp;(d={points:a});return this.el("polygon",d)};(function(){function d(){return this.selectAll("stop")}function n(b,d){var f=e("stop"),k={offset:+d+"%"};b=a.color(b);k["stop-color"]=b.hex;1&gt;b.opacity&amp;&amp;(k["stop-opacity"]=b.opacity);e(f,k);this.node.appendChild(f);return this}function u(){if("linearGradient"==this.type){var b=e(this.node,"x1")||0,d=e(this.node,"x2")|| 1,f=e(this.node,"y1")||0,k=e(this.node,"y2")||0;return a._.box(b,f,math.abs(d-b),math.abs(k-f))}b=this.node.r||0;return a._.box((this.node.cx||0.5)-b,(this.node.cy||0.5)-b,2*b,2*b)}function p(a,d){function f(a,b){for(var d=(b-u)/(a-w),e=w;e&lt;a;e++)h[e].offset=+(+u+d*(e-w)).toFixed(2);w=a;u=b}var n=k("snap.util.grad.parse",null,d).firstDefined(),p;if(!n)return null;n.params.unshift(a);p="l"==n.type.toLowerCase()?b.apply(0,n.params):q.apply(0,n.params);n.type!=n.type.toLowerCase()&amp;&amp;e(p.node,{gradientUnits:"userSpaceOnUse"}); var h=n.stops,n=h.length,u=0,w=0;n--;for(var v=0;v&lt;n;v++)"offset"in h[v]&amp;&amp;f(v,h[v].offset);h[n].offset=h[n].offset||100;f(n,h[n].offset);for(v=0;v&lt;=n;v++){var y=h[v];p.addStop(y.color,y.offset)}return p}function b(b,k,p,q,w){b=a._.make("linearGradient",b);b.stops=d;b.addStop=n;b.getBBox=u;null!=k&amp;&amp;e(b.node,{x1:k,y1:p,x2:q,y2:w});return b}function q(b,k,p,q,w,h){b=a._.make("radialGradient",b);b.stops=d;b.addStop=n;b.getBBox=u;null!=k&amp;&amp;e(b.node,{cx:k,cy:p,r:q});null!=w&amp;&amp;null!=h&amp;&amp;e(b.node,{fx:w,fy:h}); return b}var e=a._.$;w.gradient=function(a){return p(this.defs,a)};w.gradientLinear=function(a,d,e,f){return b(this.defs,a,d,e,f)};w.gradientRadial=function(a,b,d,e,f){return q(this.defs,a,b,d,e,f)};w.toString=function(){var b=this.node.ownerDocument,d=b.createDocumentFragment(),b=b.createElement("div"),e=this.node.cloneNode(!0);d.appendChild(b);b.appendChild(e);a._.$(e,{xmlns:"http://www.w3.org/2000/svg"});b=b.innerHTML;d.removeChild(d.firstChild);return b};w.clear=function(){for(var a=this.node.firstChild, b;a;)b=a.nextSibling,"defs"!=a.tagName?a.parentNode.removeChild(a):w.clear.call({node:a}),a=b}})()});C.plugin(function(a,k,y,M){function A(a){var b=A.ps=A.ps||{};b[a]?b[a].sleep=100:b[a]={sleep:100};setTimeout(function(){for(var d in b)b[L](d)&amp;&amp;d!=a&amp;&amp;(b[d].sleep--,!b[d].sleep&amp;&amp;delete b[d])});return b[a]}function w(a,b,d,e){null==a&amp;&amp;(a=b=d=e=0);null==b&amp;&amp;(b=a.y,d=a.width,e=a.height,a=a.x);return{x:a,y:b,width:d,w:d,height:e,h:e,x2:a+d,y2:b+e,cx:a+d/2,cy:b+e/2,r1:F.min(d,e)/2,r2:F.max(d,e)/2,r0:F.sqrt(d* d+e*e)/2,path:s(a,b,d,e),vb:[a,b,d,e].join(" ")}}function z(){return this.join(",").replace(N,"$1")}function d(a){a=C(a);a.toString=z;return a}function f(a,b,d,h,f,k,l,n,p){if(null==p)return e(a,b,d,h,f,k,l,n);if(0&gt;p||e(a,b,d,h,f,k,l,n)&lt;p)p=void 0;else{var q=0.5,O=1-q,s;for(s=e(a,b,d,h,f,k,l,n,O);0.01&lt;Z(s-p);)q/=2,O+=(s&lt;p?1:-1)*q,s=e(a,b,d,h,f,k,l,n,O);p=O}return u(a,b,d,h,f,k,l,n,p)}function n(b,d){function e(a){return+(+a).toFixed(3)}return a._.cacher(function(a,h,l){a instanceof k&amp;&amp;(a=a.attr("d")); a=I(a);for(var n,p,D,q,O="",s={},c=0,t=0,r=a.length;t&lt;r;t++){D=a[t];if("M"==D[0])n=+D[1],p=+D[2];else{q=f(n,p,D[1],D[2],D[3],D[4],D[5],D[6]);if(c+q&gt;h){if(d&amp;&amp;!s.start){n=f(n,p,D[1],D[2],D[3],D[4],D[5],D[6],h-c);O+=["C"+e(n.start.x),e(n.start.y),e(n.m.x),e(n.m.y),e(n.x),e(n.y)];if(l)return O;s.start=O;O=["M"+e(n.x),e(n.y)+"C"+e(n.n.x),e(n.n.y),e(n.end.x),e(n.end.y),e(D[5]),e(D[6])].join();c+=q;n=+D[5];p=+D[6];continue}if(!b&amp;&amp;!d)return n=f(n,p,D[1],D[2],D[3],D[4],D[5],D[6],h-c)}c+=q;n=+D[5];p=+D[6]}O+= D.shift()+D}s.end=O;return n=b?c:d?s:u(n,p,D[0],D[1],D[2],D[3],D[4],D[5],1)},null,a._.clone)}function u(a,b,d,e,h,f,k,l,n){var p=1-n,q=ma(p,3),s=ma(p,2),c=n*n,t=c*n,r=q*a+3*s*n*d+3*p*n*n*h+t*k,q=q*b+3*s*n*e+3*p*n*n*f+t*l,s=a+2*n*(d-a)+c*(h-2*d+a),t=b+2*n*(e-b)+c*(f-2*e+b),x=d+2*n*(h-d)+c*(k-2*h+d),c=e+2*n*(f-e)+c*(l-2*f+e);a=p*a+n*d;b=p*b+n*e;h=p*h+n*k;f=p*f+n*l;l=90-180*F.atan2(s-x,t-c)/S;return{x:r,y:q,m:{x:s,y:t},n:{x:x,y:c},start:{x:a,y:b},end:{x:h,y:f},alpha:l}}function p(b,d,e,h,f,n,k,l){a.is(b, "array")||(b=[b,d,e,h,f,n,k,l]);b=U.apply(null,b);return w(b.min.x,b.min.y,b.max.x-b.min.x,b.max.y-b.min.y)}function b(a,b,d){return b&gt;=a.x&amp;&amp;b&lt;=a.x+a.width&amp;&amp;d&gt;=a.y&amp;&amp;d&lt;=a.y+a.height}function q(a,d){a=w(a);d=w(d);return b(d,a.x,a.y)||b(d,a.x2,a.y)||b(d,a.x,a.y2)||b(d,a.x2,a.y2)||b(a,d.x,d.y)||b(a,d.x2,d.y)||b(a,d.x,d.y2)||b(a,d.x2,d.y2)||(a.x&lt;d.x2&amp;&amp;a.x&gt;d.x||d.x&lt;a.x2&amp;&amp;d.x&gt;a.x)&amp;&amp;(a.y&lt;d.y2&amp;&amp;a.y&gt;d.y||d.y&lt;a.y2&amp;&amp;d.y&gt;a.y)}function e(a,b,d,e,h,f,n,k,l){null==l&amp;&amp;(l=1);l=(1&lt;l?1:0&gt;l?0:l)/2;for(var p=[-0.1252, 0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816],q=[0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],s=0,c=0;12&gt;c;c++)var t=l*p[c]+l,r=t*(t*(-3*a+9*d-9*h+3*n)+6*a-12*d+6*h)-3*a+3*d,t=t*(t*(-3*b+9*e-9*f+3*k)+6*b-12*e+6*f)-3*b+3*e,s=s+q[c]*F.sqrt(r*r+t*t);return l*s}function l(a,b,d){a=I(a);b=I(b);for(var h,f,l,n,k,s,r,O,x,c,t=d?0:[],w=0,v=a.length;w&lt;v;w++)if(x=a[w],"M"==x[0])h=k=x[1],f=s=x[2];else{"C"==x[0]?(x=[h,f].concat(x.slice(1)), h=x[6],f=x[7]):(x=[h,f,h,f,k,s,k,s],h=k,f=s);for(var G=0,y=b.length;G&lt;y;G++)if(c=b[G],"M"==c[0])l=r=c[1],n=O=c[2];else{"C"==c[0]?(c=[l,n].concat(c.slice(1)),l=c[6],n=c[7]):(c=[l,n,l,n,r,O,r,O],l=r,n=O);var z;var K=x,B=c;z=d;var H=p(K),J=p(B);if(q(H,J)){for(var H=e.apply(0,K),J=e.apply(0,B),H=~~(H/8),J=~~(J/8),U=[],A=[],F={},M=z?0:[],P=0;P&lt;H+1;P++){var C=u.apply(0,K.concat(P/H));U.push({x:C.x,y:C.y,t:P/H})}for(P=0;P&lt;J+1;P++)C=u.apply(0,B.concat(P/J)),A.push({x:C.x,y:C.y,t:P/J});for(P=0;P&lt;H;P++)for(K= 0;K&lt;J;K++){var Q=U[P],L=U[P+1],B=A[K],C=A[K+1],N=0.001&gt;Z(L.x-Q.x)?"y":"x",S=0.001&gt;Z(C.x-B.x)?"y":"x",R;R=Q.x;var Y=Q.y,V=L.x,ea=L.y,fa=B.x,ga=B.y,ha=C.x,ia=C.y;if(W(R,V)&lt;X(fa,ha)||X(R,V)&gt;W(fa,ha)||W(Y,ea)&lt;X(ga,ia)||X(Y,ea)&gt;W(ga,ia))R=void 0;else{var $=(R*ea-Y*V)*(fa-ha)-(R-V)*(fa*ia-ga*ha),aa=(R*ea-Y*V)*(ga-ia)-(Y-ea)*(fa*ia-ga*ha),ja=(R-V)*(ga-ia)-(Y-ea)*(fa-ha);if(ja){var $=$/ja,aa=aa/ja,ja=+$.toFixed(2),ba=+aa.toFixed(2);R=ja&lt;+X(R,V).toFixed(2)||ja&gt;+W(R,V).toFixed(2)||ja&lt;+X(fa,ha).toFixed(2)|| ja&gt;+W(fa,ha).toFixed(2)||ba&lt;+X(Y,ea).toFixed(2)||ba&gt;+W(Y,ea).toFixed(2)||ba&lt;+X(ga,ia).toFixed(2)||ba&gt;+W(ga,ia).toFixed(2)?void 0:{x:$,y:aa}}else R=void 0}R&amp;&amp;F[R.x.toFixed(4)]!=R.y.toFixed(4)&amp;&amp;(F[R.x.toFixed(4)]=R.y.toFixed(4),Q=Q.t+Z((R[N]-Q[N])/(L[N]-Q[N]))*(L.t-Q.t),B=B.t+Z((R[S]-B[S])/(C[S]-B[S]))*(C.t-B.t),0&lt;=Q&amp;&amp;1&gt;=Q&amp;&amp;0&lt;=B&amp;&amp;1&gt;=B&amp;&amp;(z?M++:M.push({x:R.x,y:R.y,t1:Q,t2:B})))}z=M}else z=z?0:[];if(d)t+=z;else{H=0;for(J=z.length;H&lt;J;H++)z[H].segment1=w,z[H].segment2=G,z[H].bez1=x,z[H].bez2=c;t=t.concat(z)}}}return t} function r(a){var b=A(a);if(b.bbox)return C(b.bbox);if(!a)return w();a=I(a);for(var d=0,e=0,h=[],f=[],l,n=0,k=a.length;n&lt;k;n++)l=a[n],"M"==l[0]?(d=l[1],e=l[2],h.push(d),f.push(e)):(d=U(d,e,l[1],l[2],l[3],l[4],l[5],l[6]),h=h.concat(d.min.x,d.max.x),f=f.concat(d.min.y,d.max.y),d=l[5],e=l[6]);a=X.apply(0,h);l=X.apply(0,f);h=W.apply(0,h);f=W.apply(0,f);f=w(a,l,h-a,f-l);b.bbox=C(f);return f}function s(a,b,d,e,h){if(h)return[["M",+a+ +h,b],["l",d-2*h,0],["a",h,h,0,0,1,h,h],["l",0,e-2*h],["a",h,h,0,0,1, -h,h],["l",2*h-d,0],["a",h,h,0,0,1,-h,-h],["l",0,2*h-e],["a",h,h,0,0,1,h,-h],["z"] ];a=[["M",a,b],["l",d,0],["l",0,e],["l",-d,0],["z"] ];a.toString=z;return a}function x(a,b,d,e,h){null==h&amp;&amp;null==e&amp;&amp;(e=d);a=+a;b=+b;d=+d;e=+e;if(null!=h){var f=Math.PI/180,l=a+d*Math.cos(-e*f);a+=d*Math.cos(-h*f);var n=b+d*Math.sin(-e*f);b+=d*Math.sin(-h*f);d=[["M",l,n],["A",d,d,0,+(180&lt;h-e),0,a,b] ]}else d=[["M",a,b],["m",0,-e],["a",d,e,0,1,1,0,2*e],["a",d,e,0,1,1,0,-2*e],["z"] ];d.toString=z;return d}function G(b){var e= A(b);if(e.abs)return d(e.abs);Q(b,"array")&amp;&amp;Q(b&amp;&amp;b[0],"array")||(b=a.parsePathString(b));if(!b||!b.length)return[["M",0,0] ];var h=[],f=0,l=0,n=0,k=0,p=0;"M"==b[0][0]&amp;&amp;(f=+b[0][1],l=+b[0][2],n=f,k=l,p++,h[0]=["M",f,l]);for(var q=3==b.length&amp;&amp;"M"==b[0][0]&amp;&amp;"R"==b[1][0].toUpperCase()&amp;&amp;"Z"==b[2][0].toUpperCase(),s,r,w=p,c=b.length;w&lt;c;w++){h.push(s=[]);r=b[w];p=r[0];if(p!=p.toUpperCase())switch(s[0]=p.toUpperCase(),s[0]){case "A":s[1]=r[1];s[2]=r[2];s[3]=r[3];s[4]=r[4];s[5]=r[5];s[6]=+r[6]+f;s[7]=+r[7]+ l;break;case "V":s[1]=+r[1]+l;break;case "H":s[1]=+r[1]+f;break;case "R":for(var t=[f,l].concat(r.slice(1)),u=2,v=t.length;u&lt;v;u++)t[u]=+t[u]+f,t[++u]=+t[u]+l;h.pop();h=h.concat(P(t,q));break;case "O":h.pop();t=x(f,l,r[1],r[2]);t.push(t[0]);h=h.concat(t);break;case "U":h.pop();h=h.concat(x(f,l,r[1],r[2],r[3]));s=["U"].concat(h[h.length-1].slice(-2));break;case "M":n=+r[1]+f,k=+r[2]+l;default:for(u=1,v=r.length;u&lt;v;u++)s[u]=+r[u]+(u%2?f:l)}else if("R"==p)t=[f,l].concat(r.slice(1)),h.pop(),h=h.concat(P(t, q)),s=["R"].concat(r.slice(-2));else if("O"==p)h.pop(),t=x(f,l,r[1],r[2]),t.push(t[0]),h=h.concat(t);else if("U"==p)h.pop(),h=h.concat(x(f,l,r[1],r[2],r[3])),s=["U"].concat(h[h.length-1].slice(-2));else for(t=0,u=r.length;t&lt;u;t++)s[t]=r[t];p=p.toUpperCase();if("O"!=p)switch(s[0]){case "Z":f=+n;l=+k;break;case "H":f=s[1];break;case "V":l=s[1];break;case "M":n=s[s.length-2],k=s[s.length-1];default:f=s[s.length-2],l=s[s.length-1]}}h.toString=z;e.abs=d(h);return h}function h(a,b,d,e){return[a,b,d,e,d, e]}function J(a,b,d,e,h,f){var l=1/3,n=2/3;return[l*a+n*d,l*b+n*e,l*h+n*d,l*f+n*e,h,f]}function K(b,d,e,h,f,l,n,k,p,s){var r=120*S/180,q=S/180*(+f||0),c=[],t,x=a._.cacher(function(a,b,c){var d=a*F.cos(c)-b*F.sin(c);a=a*F.sin(c)+b*F.cos(c);return{x:d,y:a}});if(s)v=s[0],t=s[1],l=s[2],u=s[3];else{t=x(b,d,-q);b=t.x;d=t.y;t=x(k,p,-q);k=t.x;p=t.y;F.cos(S/180*f);F.sin(S/180*f);t=(b-k)/2;v=(d-p)/2;u=t*t/(e*e)+v*v/(h*h);1&lt;u&amp;&amp;(u=F.sqrt(u),e*=u,h*=u);var u=e*e,w=h*h,u=(l==n?-1:1)*F.sqrt(Z((u*w-u*v*v-w*t*t)/ (u*v*v+w*t*t)));l=u*e*v/h+(b+k)/2;var u=u*-h*t/e+(d+p)/2,v=F.asin(((d-u)/h).toFixed(9));t=F.asin(((p-u)/h).toFixed(9));v=b&lt;l?S-v:v;t=k&lt;l?S-t:t;0&gt;v&amp;&amp;(v=2*S+v);0&gt;t&amp;&amp;(t=2*S+t);n&amp;&amp;v&gt;t&amp;&amp;(v-=2*S);!n&amp;&amp;t&gt;v&amp;&amp;(t-=2*S)}if(Z(t-v)&gt;r){var c=t,w=k,G=p;t=v+r*(n&amp;&amp;t&gt;v?1:-1);k=l+e*F.cos(t);p=u+h*F.sin(t);c=K(k,p,e,h,f,0,n,w,G,[t,c,l,u])}l=t-v;f=F.cos(v);r=F.sin(v);n=F.cos(t);t=F.sin(t);l=F.tan(l/4);e=4/3*e*l;l*=4/3*h;h=[b,d];b=[b+e*r,d-l*f];d=[k+e*t,p-l*n];k=[k,p];b[0]=2*h[0]-b[0];b[1]=2*h[1]-b[1];if(s)return[b,d,k].concat(c); c=[b,d,k].concat(c).join().split(",");s=[];k=0;for(p=c.length;k&lt;p;k++)s[k]=k%2?x(c[k-1],c[k],q).y:x(c[k],c[k+1],q).x;return s}function U(a,b,d,e,h,f,l,k){for(var n=[],p=[[],[] ],s,r,c,t,q=0;2&gt;q;++q)0==q?(r=6*a-12*d+6*h,s=-3*a+9*d-9*h+3*l,c=3*d-3*a):(r=6*b-12*e+6*f,s=-3*b+9*e-9*f+3*k,c=3*e-3*b),1E-12&gt;Z(s)?1E-12&gt;Z(r)||(s=-c/r,0&lt;s&amp;&amp;1&gt;s&amp;&amp;n.push(s)):(t=r*r-4*c*s,c=F.sqrt(t),0&gt;t||(t=(-r+c)/(2*s),0&lt;t&amp;&amp;1&gt;t&amp;&amp;n.push(t),s=(-r-c)/(2*s),0&lt;s&amp;&amp;1&gt;s&amp;&amp;n.push(s)));for(r=q=n.length;q--;)s=n[q],c=1-s,p[0][q]=c*c*c*a+3* c*c*s*d+3*c*s*s*h+s*s*s*l,p[1][q]=c*c*c*b+3*c*c*s*e+3*c*s*s*f+s*s*s*k;p[0][r]=a;p[1][r]=b;p[0][r+1]=l;p[1][r+1]=k;p[0].length=p[1].length=r+2;return{min:{x:X.apply(0,p[0]),y:X.apply(0,p[1])},max:{x:W.apply(0,p[0]),y:W.apply(0,p[1])}}}function I(a,b){var e=!b&amp;&amp;A(a);if(!b&amp;&amp;e.curve)return d(e.curve);var f=G(a),l=b&amp;&amp;G(b),n={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},k={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},p=function(a,b,c){if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];a[0]in{T:1,Q:1}||(b.qx=b.qy=null); switch(a[0]){case "M":b.X=a[1];b.Y=a[2];break;case "A":a=["C"].concat(K.apply(0,[b.x,b.y].concat(a.slice(1))));break;case "S":"C"==c||"S"==c?(c=2*b.x-b.bx,b=2*b.y-b.by):(c=b.x,b=b.y);a=["C",c,b].concat(a.slice(1));break;case "T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y);a=["C"].concat(J(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case "Q":b.qx=a[1];b.qy=a[2];a=["C"].concat(J(b.x,b.y,a[1],a[2],a[3],a[4]));break;case "L":a=["C"].concat(h(b.x,b.y,a[1],a[2]));break;case "H":a=["C"].concat(h(b.x, b.y,a[1],b.y));break;case "V":a=["C"].concat(h(b.x,b.y,b.x,a[1]));break;case "Z":a=["C"].concat(h(b.x,b.y,b.X,b.Y))}return a},s=function(a,b){if(7&lt;a[b].length){a[b].shift();for(var c=a[b];c.length;)q[b]="A",l&amp;&amp;(u[b]="A"),a.splice(b++,0,["C"].concat(c.splice(0,6)));a.splice(b,1);v=W(f.length,l&amp;&amp;l.length||0)}},r=function(a,b,c,d,e){a&amp;&amp;b&amp;&amp;"M"==a[e][0]&amp;&amp;"M"!=b[e][0]&amp;&amp;(b.splice(e,0,["M",d.x,d.y]),c.bx=0,c.by=0,c.x=a[e][1],c.y=a[e][2],v=W(f.length,l&amp;&amp;l.length||0))},q=[],u=[],c="",t="",x=0,v=W(f.length, l&amp;&amp;l.length||0);for(;x&lt;v;x++){f[x]&amp;&amp;(c=f[x][0]);"C"!=c&amp;&amp;(q[x]=c,x&amp;&amp;(t=q[x-1]));f[x]=p(f[x],n,t);"A"!=q[x]&amp;&amp;"C"==c&amp;&amp;(q[x]="C");s(f,x);l&amp;&amp;(l[x]&amp;&amp;(c=l[x][0]),"C"!=c&amp;&amp;(u[x]=c,x&amp;&amp;(t=u[x-1])),l[x]=p(l[x],k,t),"A"!=u[x]&amp;&amp;"C"==c&amp;&amp;(u[x]="C"),s(l,x));r(f,l,n,k,x);r(l,f,k,n,x);var w=f[x],z=l&amp;&amp;l[x],y=w.length,U=l&amp;&amp;z.length;n.x=w[y-2];n.y=w[y-1];n.bx=$(w[y-4])||n.x;n.by=$(w[y-3])||n.y;k.bx=l&amp;&amp;($(z[U-4])||k.x);k.by=l&amp;&amp;($(z[U-3])||k.y);k.x=l&amp;&amp;z[U-2];k.y=l&amp;&amp;z[U-1]}l||(e.curve=d(f));return l?[f,l]:f}function P(a, b){for(var d=[],e=0,h=a.length;h-2*!b&gt;e;e+=2){var f=[{x:+a[e-2],y:+a[e-1]},{x:+a[e],y:+a[e+1]},{x:+a[e+2],y:+a[e+3]},{x:+a[e+4],y:+a[e+5]}];b?e?h-4==e?f[3]={x:+a[0],y:+a[1]}:h-2==e&amp;&amp;(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[h-2],y:+a[h-1]}:h-4==e?f[3]=f[2]:e||(f[0]={x:+a[e],y:+a[e+1]});d.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return d}y=k.prototype;var Q=a.is,C=a._.clone,L="hasOwnProperty", N=/,?([a-z]),?/gi,$=parseFloat,F=Math,S=F.PI,X=F.min,W=F.max,ma=F.pow,Z=F.abs;M=n(1);var na=n(),ba=n(0,1),V=a._unit2px;a.path=A;a.path.getTotalLength=M;a.path.getPointAtLength=na;a.path.getSubpath=function(a,b,d){if(1E-6&gt;this.getTotalLength(a)-d)return ba(a,b).end;a=ba(a,d,1);return b?ba(a,b).end:a};y.getTotalLength=function(){if(this.node.getTotalLength)return this.node.getTotalLength()};y.getPointAtLength=function(a){return na(this.attr("d"),a)};y.getSubpath=function(b,d){return a.path.getSubpath(this.attr("d"), b,d)};a._.box=w;a.path.findDotsAtSegment=u;a.path.bezierBBox=p;a.path.isPointInsideBBox=b;a.path.isBBoxIntersect=q;a.path.intersection=function(a,b){return l(a,b)};a.path.intersectionNumber=function(a,b){return l(a,b,1)};a.path.isPointInside=function(a,d,e){var h=r(a);return b(h,d,e)&amp;&amp;1==l(a,[["M",d,e],["H",h.x2+10] ],1)%2};a.path.getBBox=r;a.path.get={path:function(a){return a.attr("path")},circle:function(a){a=V(a);return x(a.cx,a.cy,a.r)},ellipse:function(a){a=V(a);return x(a.cx||0,a.cy||0,a.rx, a.ry)},rect:function(a){a=V(a);return s(a.x||0,a.y||0,a.width,a.height,a.rx,a.ry)},image:function(a){a=V(a);return s(a.x||0,a.y||0,a.width,a.height)},line:function(a){return"M"+[a.attr("x1")||0,a.attr("y1")||0,a.attr("x2"),a.attr("y2")]},polyline:function(a){return"M"+a.attr("points")},polygon:function(a){return"M"+a.attr("points")+"z"},deflt:function(a){a=a.node.getBBox();return s(a.x,a.y,a.width,a.height)}};a.path.toRelative=function(b){var e=A(b),h=String.prototype.toLowerCase;if(e.rel)return d(e.rel); a.is(b,"array")&amp;&amp;a.is(b&amp;&amp;b[0],"array")||(b=a.parsePathString(b));var f=[],l=0,n=0,k=0,p=0,s=0;"M"==b[0][0]&amp;&amp;(l=b[0][1],n=b[0][2],k=l,p=n,s++,f.push(["M",l,n]));for(var r=b.length;s&lt;r;s++){var q=f[s]=[],x=b[s];if(x[0]!=h.call(x[0]))switch(q[0]=h.call(x[0]),q[0]){case "a":q[1]=x[1];q[2]=x[2];q[3]=x[3];q[4]=x[4];q[5]=x[5];q[6]=+(x[6]-l).toFixed(3);q[7]=+(x[7]-n).toFixed(3);break;case "v":q[1]=+(x[1]-n).toFixed(3);break;case "m":k=x[1],p=x[2];default:for(var c=1,t=x.length;c&lt;t;c++)q[c]=+(x[c]-(c%2?l: n)).toFixed(3)}else for(f[s]=[],"m"==x[0]&amp;&amp;(k=x[1]+l,p=x[2]+n),q=0,c=x.length;q&lt;c;q++)f[s][q]=x[q];x=f[s].length;switch(f[s][0]){case "z":l=k;n=p;break;case "h":l+=+f[s][x-1];break;case "v":n+=+f[s][x-1];break;default:l+=+f[s][x-2],n+=+f[s][x-1]}}f.toString=z;e.rel=d(f);return f};a.path.toAbsolute=G;a.path.toCubic=I;a.path.map=function(a,b){if(!b)return a;var d,e,h,f,l,n,k;a=I(a);h=0;for(l=a.length;h&lt;l;h++)for(k=a[h],f=1,n=k.length;f&lt;n;f+=2)d=b.x(k[f],k[f+1]),e=b.y(k[f],k[f+1]),k[f]=d,k[f+1]=e;return a}; a.path.toString=z;a.path.clone=d});C.plugin(function(a,v,y,C){var A=Math.max,w=Math.min,z=function(a){this.items=[];this.bindings={};this.length=0;this.type="set";if(a)for(var f=0,n=a.length;f&lt;n;f++)a[f]&amp;&amp;(this[this.items.length]=this.items[this.items.length]=a[f],this.length++)};v=z.prototype;v.push=function(){for(var a,f,n=0,k=arguments.length;n&lt;k;n++)if(a=arguments[n])f=this.items.length,this[f]=this.items[f]=a,this.length++;return this};v.pop=function(){this.length&amp;&amp;delete this[this.length--]; return this.items.pop()};v.forEach=function(a,f){for(var n=0,k=this.items.length;n&lt;k&amp;&amp;!1!==a.call(f,this.items[n],n);n++);return this};v.animate=function(d,f,n,u){"function"!=typeof n||n.length||(u=n,n=L.linear);d instanceof a._.Animation&amp;&amp;(u=d.callback,n=d.easing,f=n.dur,d=d.attr);var p=arguments;if(a.is(d,"array")&amp;&amp;a.is(p[p.length-1],"array"))var b=!0;var q,e=function(){q?this.b=q:q=this.b},l=0,r=u&amp;&amp;function(){l++==this.length&amp;&amp;u.call(this)};return this.forEach(function(a,l){k.once("snap.animcreated."+ a.id,e);b?p[l]&amp;&amp;a.animate.apply(a,p[l]):a.animate(d,f,n,r)})};v.remove=function(){for(;this.length;)this.pop().remove();return this};v.bind=function(a,f,k){var u={};if("function"==typeof f)this.bindings[a]=f;else{var p=k||a;this.bindings[a]=function(a){u[p]=a;f.attr(u)}}return this};v.attr=function(a){var f={},k;for(k in a)if(this.bindings[k])this.bindings[k](a[k]);else f[k]=a[k];a=0;for(k=this.items.length;a&lt;k;a++)this.items[a].attr(f);return this};v.clear=function(){for(;this.length;)this.pop()}; v.splice=function(a,f,k){a=0&gt;a?A(this.length+a,0):a;f=A(0,w(this.length-a,f));var u=[],p=[],b=[],q;for(q=2;q&lt;arguments.length;q++)b.push(arguments[q]);for(q=0;q&lt;f;q++)p.push(this[a+q]);for(;q&lt;this.length-a;q++)u.push(this[a+q]);var e=b.length;for(q=0;q&lt;e+u.length;q++)this.items[a+q]=this[a+q]=q&lt;e?b[q]:u[q-e];for(q=this.items.length=this.length-=f-e;this[q];)delete this[q++];return new z(p)};v.exclude=function(a){for(var f=0,k=this.length;f&lt;k;f++)if(this[f]==a)return this.splice(f,1),!0;return!1}; v.insertAfter=function(a){for(var f=this.items.length;f--;)this.items[f].insertAfter(a);return this};v.getBBox=function(){for(var a=[],f=[],k=[],u=[],p=this.items.length;p--;)if(!this.items[p].removed){var b=this.items[p].getBBox();a.push(b.x);f.push(b.y);k.push(b.x+b.width);u.push(b.y+b.height)}a=w.apply(0,a);f=w.apply(0,f);k=A.apply(0,k);u=A.apply(0,u);return{x:a,y:f,x2:k,y2:u,width:k-a,height:u-f,cx:a+(k-a)/2,cy:f+(u-f)/2}};v.clone=function(a){a=new z;for(var f=0,k=this.items.length;f&lt;k;f++)a.push(this.items[f].clone()); return a};v.toString=function(){return"Snap\u2018s set"};v.type="set";a.set=function(){var a=new z;arguments.length&amp;&amp;a.push.apply(a,Array.prototype.slice.call(arguments,0));return a}});C.plugin(function(a,v,y,C){function A(a){var b=a[0];switch(b.toLowerCase()){case "t":return[b,0,0];case "m":return[b,1,0,0,1,0,0];case "r":return 4==a.length?[b,0,a[2],a[3] ]:[b,0];case "s":return 5==a.length?[b,1,1,a[3],a[4] ]:3==a.length?[b,1,1]:[b,1]}}function w(b,d,f){d=q(d).replace(/\.{3}|\u2026/g,b);b=a.parseTransformString(b)|| [];d=a.parseTransformString(d)||[];for(var k=Math.max(b.length,d.length),p=[],v=[],h=0,w,z,y,I;h&lt;k;h++){y=b[h]||A(d[h]);I=d[h]||A(y);if(y[0]!=I[0]||"r"==y[0].toLowerCase()&amp;&amp;(y[2]!=I[2]||y[3]!=I[3])||"s"==y[0].toLowerCase()&amp;&amp;(y[3]!=I[3]||y[4]!=I[4])){b=a._.transform2matrix(b,f());d=a._.transform2matrix(d,f());p=[["m",b.a,b.b,b.c,b.d,b.e,b.f] ];v=[["m",d.a,d.b,d.c,d.d,d.e,d.f] ];break}p[h]=[];v[h]=[];w=0;for(z=Math.max(y.length,I.length);w&lt;z;w++)w in y&amp;&amp;(p[h][w]=y[w]),w in I&amp;&amp;(v[h][w]=I[w])}return{from:u(p), to:u(v),f:n(p)}}function z(a){return a}function d(a){return function(b){return+b.toFixed(3)+a}}function f(b){return a.rgb(b[0],b[1],b[2])}function n(a){var b=0,d,f,k,n,h,p,q=[];d=0;for(f=a.length;d&lt;f;d++){h="[";p=['"'+a[d][0]+'"'];k=1;for(n=a[d].length;k&lt;n;k++)p[k]="val["+b++ +"]";h+=p+"]";q[d]=h}return Function("val","return Snap.path.toString.call(["+q+"])")}function u(a){for(var b=[],d=0,f=a.length;d&lt;f;d++)for(var k=1,n=a[d].length;k&lt;n;k++)b.push(a[d][k]);return b}var p={},b=/[a-z]+$/i,q=String; p.stroke=p.fill="colour";v.prototype.equal=function(a,b){return k("snap.util.equal",this,a,b).firstDefined()};k.on("snap.util.equal",function(e,k){var r,s;r=q(this.attr(e)||"");var x=this;if(r==+r&amp;&amp;k==+k)return{from:+r,to:+k,f:z};if("colour"==p[e])return r=a.color(r),s=a.color(k),{from:[r.r,r.g,r.b,r.opacity],to:[s.r,s.g,s.b,s.opacity],f:f};if("transform"==e||"gradientTransform"==e||"patternTransform"==e)return k instanceof a.Matrix&amp;&amp;(k=k.toTransformString()),a._.rgTransform.test(k)||(k=a._.svgTransform2string(k)), w(r,k,function(){return x.getBBox(1)});if("d"==e||"path"==e)return r=a.path.toCubic(r,k),{from:u(r[0]),to:u(r[1]),f:n(r[0])};if("points"==e)return r=q(r).split(a._.separator),s=q(k).split(a._.separator),{from:r,to:s,f:function(a){return a}};aUnit=r.match(b);s=q(k).match(b);return aUnit&amp;&amp;aUnit==s?{from:parseFloat(r),to:parseFloat(k),f:d(aUnit)}:{from:this.asPX(e),to:this.asPX(e,k),f:z}})});C.plugin(function(a,v,y,C){var A=v.prototype,w="createTouch"in C.doc;v="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel".split(" "); var z={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},d=function(a,b){var d="y"==a?"scrollTop":"scrollLeft",e=b&amp;&amp;b.node?b.node.ownerDocument:C.doc;return e[d in e.documentElement?"documentElement":"body"][d]},f=function(){this.returnValue=!1},n=function(){return this.originalEvent.preventDefault()},u=function(){this.cancelBubble=!0},p=function(){return this.originalEvent.stopPropagation()},b=function(){if(C.doc.addEventListener)return function(a,b,e,f){var k=w&amp;&amp;z[b]?z[b]:b,l=function(k){var l= d("y",f),q=d("x",f);if(w&amp;&amp;z.hasOwnProperty(b))for(var r=0,u=k.targetTouches&amp;&amp;k.targetTouches.length;r&lt;u;r++)if(k.targetTouches[r].target==a||a.contains(k.targetTouches[r].target)){u=k;k=k.targetTouches[r];k.originalEvent=u;k.preventDefault=n;k.stopPropagation=p;break}return e.call(f,k,k.clientX+q,k.clientY+l)};b!==k&amp;&amp;a.addEventListener(b,l,!1);a.addEventListener(k,l,!1);return function(){b!==k&amp;&amp;a.removeEventListener(b,l,!1);a.removeEventListener(k,l,!1);return!0}};if(C.doc.attachEvent)return function(a, b,e,h){var k=function(a){a=a||h.node.ownerDocument.window.event;var b=d("y",h),k=d("x",h),k=a.clientX+k,b=a.clientY+b;a.preventDefault=a.preventDefault||f;a.stopPropagation=a.stopPropagation||u;return e.call(h,a,k,b)};a.attachEvent("on"+b,k);return function(){a.detachEvent("on"+b,k);return!0}}}(),q=[],e=function(a){for(var b=a.clientX,e=a.clientY,f=d("y"),l=d("x"),n,p=q.length;p--;){n=q[p];if(w)for(var r=a.touches&amp;&amp;a.touches.length,u;r--;){if(u=a.touches[r],u.identifier==n.el._drag.id||n.el.node.contains(u.target)){b= u.clientX;e=u.clientY;(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();b+=l;e+=f;k("snap.drag.move."+n.el.id,n.move_scope||n.el,b-n.el._drag.x,e-n.el._drag.y,b,e,a)}},l=function(b){a.unmousemove(e).unmouseup(l);for(var d=q.length,f;d--;)f=q[d],f.el._drag={},k("snap.drag.end."+f.el.id,f.end_scope||f.start_scope||f.move_scope||f.el,b);q=[]};for(y=v.length;y--;)(function(d){a[d]=A[d]=function(e,f){a.is(e,"function")&amp;&amp;(this.events=this.events||[],this.events.push({name:d, f:e,unbind:b(this.node||document,d,e,f||this)}));return this};a["un"+d]=A["un"+d]=function(a){for(var b=this.events||[],e=b.length;e--;)if(b[e].name==d&amp;&amp;(b[e].f==a||!a)){b[e].unbind();b.splice(e,1);!b.length&amp;&amp;delete this.events;break}return this}})(v[y]);A.hover=function(a,b,d,e){return this.mouseover(a,d).mouseout(b,e||d)};A.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var r=[];A.drag=function(b,d,f,h,n,p){function u(r,v,w){(r.originalEvent||r).preventDefault();this._drag.x=v; this._drag.y=w;this._drag.id=r.identifier;!q.length&amp;&amp;a.mousemove(e).mouseup(l);q.push({el:this,move_scope:h,start_scope:n,end_scope:p});d&amp;&amp;k.on("snap.drag.start."+this.id,d);b&amp;&amp;k.on("snap.drag.move."+this.id,b);f&amp;&amp;k.on("snap.drag.end."+this.id,f);k("snap.drag.start."+this.id,n||h||this,v,w,r)}if(!arguments.length){var v;return this.drag(function(a,b){this.attr({transform:v+(v?"T":"t")+[a,b]})},function(){v=this.transform().local})}this._drag={};r.push({el:this,start:u});this.mousedown(u);return this}; A.undrag=function(){for(var b=r.length;b--;)r[b].el==this&amp;&amp;(this.unmousedown(r[b].start),r.splice(b,1),k.unbind("snap.drag.*."+this.id));!r.length&amp;&amp;a.unmousemove(e).unmouseup(l);return this}});C.plugin(function(a,v,y,C){y=y.prototype;var A=/^\s*url\((.+)\)/,w=String,z=a._.$;a.filter={};y.filter=function(d){var f=this;"svg"!=f.type&amp;&amp;(f=f.paper);d=a.parse(w(d));var k=a._.id(),u=z("filter");z(u,{id:k,filterUnits:"userSpaceOnUse"});u.appendChild(d.node);f.defs.appendChild(u);return new v(u)};k.on("snap.util.getattr.filter", function(){k.stop();var d=z(this.node,"filter");if(d)return(d=w(d).match(A))&amp;&amp;a.select(d[1])});k.on("snap.util.attr.filter",function(d){if(d instanceof v&amp;&amp;"filter"==d.type){k.stop();var f=d.node.id;f||(z(d.node,{id:d.id}),f=d.id);z(this.node,{filter:a.url(f)})}d&amp;&amp;"none"!=d||(k.stop(),this.node.removeAttribute("filter"))});a.filter.blur=function(d,f){null==d&amp;&amp;(d=2);return a.format('&lt;feGaussianBlur stdDeviation="{def}"/&gt;',{def:null==f?d:[d,f]})};a.filter.blur.toString=function(){return this()};a.filter.shadow= function(d,f,k,u,p){"string"==typeof k&amp;&amp;(p=u=k,k=4);"string"!=typeof u&amp;&amp;(p=u,u="#000");null==k&amp;&amp;(k=4);null==p&amp;&amp;(p=1);null==d&amp;&amp;(d=0,f=2);null==f&amp;&amp;(f=d);u=a.color(u||"#000");return a.format('&lt;feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/&gt;&lt;feOffset dx="{dx}" dy="{dy}" result="offsetblur"/&gt;&lt;feFlood flood-color="{color}"/&gt;&lt;feComposite in2="offsetblur" operator="in"/&gt;&lt;feComponentTransfer&gt;&lt;feFuncA type="linear" slope="{opacity}"/&gt;&lt;/feComponentTransfer&gt;&lt;feMerge&gt;&lt;feMergeNode/&gt;&lt;feMergeNode in="SourceGraphic"/&gt;&lt;/feMerge&gt;', {color:u,dx:d,dy:f,blur:k,opacity:p})};a.filter.shadow.toString=function(){return this()};a.filter.grayscale=function(d){null==d&amp;&amp;(d=1);return a.format('&lt;feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {b} {h} 0 0 0 0 0 1 0"/&gt;',{a:0.2126+0.7874*(1-d),b:0.7152-0.7152*(1-d),c:0.0722-0.0722*(1-d),d:0.2126-0.2126*(1-d),e:0.7152+0.2848*(1-d),f:0.0722-0.0722*(1-d),g:0.2126-0.2126*(1-d),h:0.0722+0.9278*(1-d)})};a.filter.grayscale.toString=function(){return this()};a.filter.sepia= function(d){null==d&amp;&amp;(d=1);return a.format('&lt;feColorMatrix type="matrix" values="{a} {b} {c} 0 0 {d} {e} {f} 0 0 {g} {h} {i} 0 0 0 0 0 1 0"/&gt;',{a:0.393+0.607*(1-d),b:0.769-0.769*(1-d),c:0.189-0.189*(1-d),d:0.349-0.349*(1-d),e:0.686+0.314*(1-d),f:0.168-0.168*(1-d),g:0.272-0.272*(1-d),h:0.534-0.534*(1-d),i:0.131+0.869*(1-d)})};a.filter.sepia.toString=function(){return this()};a.filter.saturate=function(d){null==d&amp;&amp;(d=1);return a.format('&lt;feColorMatrix type="saturate" values="{amount}"/&gt;',{amount:1- d})};a.filter.saturate.toString=function(){return this()};a.filter.hueRotate=function(d){return a.format('&lt;feColorMatrix type="hueRotate" values="{angle}"/&gt;',{angle:d||0})};a.filter.hueRotate.toString=function(){return this()};a.filter.invert=function(d){null==d&amp;&amp;(d=1);return a.format('&lt;feComponentTransfer&gt;&lt;feFuncR type="table" tableValues="{amount} {amount2}"/&gt;&lt;feFuncG type="table" tableValues="{amount} {amount2}"/&gt;&lt;feFuncB type="table" tableValues="{amount} {amount2}"/&gt;&lt;/feComponentTransfer&gt;',{amount:d, amount2:1-d})};a.filter.invert.toString=function(){return this()};a.filter.brightness=function(d){null==d&amp;&amp;(d=1);return a.format('&lt;feComponentTransfer&gt;&lt;feFuncR type="linear" slope="{amount}"/&gt;&lt;feFuncG type="linear" slope="{amount}"/&gt;&lt;feFuncB type="linear" slope="{amount}"/&gt;&lt;/feComponentTransfer&gt;',{amount:d})};a.filter.brightness.toString=function(){return this()};a.filter.contrast=function(d){null==d&amp;&amp;(d=1);return a.format('&lt;feComponentTransfer&gt;&lt;feFuncR type="linear" slope="{amount}" intercept="{amount2}"/&gt;&lt;feFuncG type="linear" slope="{amount}" intercept="{amount2}"/&gt;&lt;feFuncB type="linear" slope="{amount}" intercept="{amount2}"/&gt;&lt;/feComponentTransfer&gt;', {amount:d,amount2:0.5-d/2})};a.filter.contrast.toString=function(){return this()}});return C}); // ]]></script> <script>// <![CDATA[ (function (glob, factory) { // AMD support if (typeof define === "function" &amp;&amp; define.amd) { // Define as an anonymous module define("Gadfly", ["Snap.svg"], function (Snap) { return factory(Snap); }); } else { // Browser globals (glob is window) // Snap adds itself to window glob.Gadfly = factory(glob.Snap); } }(this, function (Snap) { var Gadfly = {}; // Get an x/y coordinate value in pixels var xPX = function(fig, x) { var client_box = fig.node.getBoundingClientRect(); return x * fig.node.viewBox.baseVal.width / client_box.width; }; var yPX = function(fig, y) { var client_box = fig.node.getBoundingClientRect(); return y * fig.node.viewBox.baseVal.height / client_box.height; }; Snap.plugin(function (Snap, Element, Paper, global) { // Traverse upwards from a snap element to find and return the first // note with the "plotroot" class. Element.prototype.plotroot = function () { var element = this; while (!element.hasClass("plotroot") &amp;&amp; element.parent() != null) { element = element.parent(); } return element; }; Element.prototype.svgroot = function () { var element = this; while (element.node.nodeName != "svg" &amp;&amp; element.parent() != null) { element = element.parent(); } return element; }; Element.prototype.plotbounds = function () { var root = this.plotroot() var bbox = root.select(".guide.background").node.getBBox(); return { x0: bbox.x, x1: bbox.x + bbox.width, y0: bbox.y, y1: bbox.y + bbox.height }; }; Element.prototype.plotcenter = function () { var root = this.plotroot() var bbox = root.select(".guide.background").node.getBBox(); return { x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2 }; }; // Emulate IE style mouseenter/mouseleave events, since Microsoft always // does everything right. // See: http://www.dynamic-tools.net/toolbox/isMouseLeaveOrEnter/ var events = ["mouseenter", "mouseleave"]; for (i in events) { (function (event_name) { var event_name = events[i]; Element.prototype[event_name] = function (fn, scope) { if (Snap.is(fn, "function")) { var fn2 = function (event) { if (event.type != "mouseover" &amp;&amp; event.type != "mouseout") { return; } var reltg = event.relatedTarget ? event.relatedTarget : event.type == "mouseout" ? event.toElement : event.fromElement; while (reltg &amp;&amp; reltg != this.node) reltg = reltg.parentNode; if (reltg != this.node) { return fn.apply(this, event); } }; if (event_name == "mouseenter") { this.mouseover(fn2, scope); } else { this.mouseout(fn2, scope); } } return this; }; })(events[i]); } Element.prototype.mousewheel = function (fn, scope) { if (Snap.is(fn, "function")) { var el = this; var fn2 = function (event) { fn.apply(el, [event]); }; } this.node.addEventListener( /Firefox/i.test(navigator.userAgent) ? "DOMMouseScroll" : "mousewheel", fn2); return this; }; // Snap's attr function can be too slow for things like panning/zooming. // This is a function to directly update element attributes without going // through eve. Element.prototype.attribute = function(key, val) { if (val === undefined) { return this.node.getAttribute(key); } else { this.node.setAttribute(key, val); return this; } }; Element.prototype.init_gadfly = function() { this.mouseenter(Gadfly.plot_mouseover) .mousemove(Gadfly.plot_mousemove) .mouseleave(Gadfly.plot_mouseout) .dblclick(Gadfly.plot_dblclick) .mousewheel(Gadfly.guide_background_scroll) .drag(Gadfly.guide_background_drag_onmove, Gadfly.guide_background_drag_onstart, Gadfly.guide_background_drag_onend); this.mouseenter(function (event)&nbsp;{ init_pan_zoom(this.plotroot()); }); return this; }; }); Gadfly.plot_mousemove = function(event, x_px, y_px) { var root = this.plotroot(); if (root.data("crosshair")) { px_per_mm = root.data("px_per_mm"); bB = root.select('boundingbox').node.getAttribute('value').split(' '); uB = root.select('unitbox').node.getAttribute('value').split(' '); xscale = root.data("xscale"); yscale = root.data("yscale"); xtranslate = root.data("tx"); ytranslate = root.data("ty"); xoff_mm = bB[0].substr(0,bB[0].length-2)/1; yoff_mm = bB[1].substr(0,bB[1].length-2)/1; xoff_unit = uB[0]/1; yoff_unit = uB[1]/1; mm_per_xunit = bB[2].substr(0,bB[2].length-2) / uB[2]; mm_per_yunit = bB[3].substr(0,bB[3].length-2) / uB[3]; x_unit = ((x_px / px_per_mm - xtranslate) / xscale - xoff_mm) / mm_per_xunit + xoff_unit; y_unit = ((y_px / px_per_mm - ytranslate) / yscale - yoff_mm) / mm_per_yunit + yoff_unit; root.select('.crosshair').select('.primitive').select('text') .node.innerHTML = x_unit.toPrecision(3)+","+y_unit.toPrecision(3); }; }; Gadfly.helpscreen_visible = function(event) { helpscreen_visible(this.plotroot()); }; var helpscreen_visible = function(root) { root.select(".helpscreen").animate({opacity: 1.0}, 250); }; Gadfly.helpscreen_hidden = function(event) { helpscreen_hidden(this.plotroot()); }; var helpscreen_hidden = function(root) { root.select(".helpscreen").animate({opacity: 0.0}, 250); }; // When the plot is moused over, emphasize the grid lines. Gadfly.plot_mouseover = function(event) { var root = this.plotroot(); var keyboard_help = function(event) { if (event.which == 191) { // ? helpscreen_visible(root); } }; root.data("keyboard_help", keyboard_help); window.addEventListener("keydown", keyboard_help); var keyboard_pan_zoom = function(event) { var bounds = root.plotbounds(), width = bounds.x1 - bounds.x0; height = bounds.y1 - bounds.y0; if (event.which == 187 || event.which == 73) { // plus or i increase_zoom_by_position(root, 0.1, true); } else if (event.which == 189 || event.which == 79) { // minus or o increase_zoom_by_position(root, -0.1, true); } else if (event.which == 39 || event.which == 76) { // right-arrow or l set_plot_pan_zoom(root, root.data("tx")-width/10, root.data("ty"), root.data("xscale"), root.data("yscale")); } else if (event.which == 40 || event.which == 74) { // down-arrow or j set_plot_pan_zoom(root, root.data("tx"), root.data("ty")-height/10, root.data("xscale"), root.data("yscale")); } else if (event.which == 37 || event.which == 72) { // left-arrow or h set_plot_pan_zoom(root, root.data("tx")+width/10, root.data("ty"), root.data("xscale"), root.data("yscale")); } else if (event.which == 38 || event.which == 75) { // up-arrow or k set_plot_pan_zoom(root, root.data("tx"), root.data("ty")+height/10, root.data("xscale"), root.data("yscale")); } else if (event.which == 82) { // r set_plot_pan_zoom(root, 0.0, 0.0, 1.0, 1.0); } else if (event.which == 191) { // ? helpscreen_hidden(root); } else if (event.which == 67) { // c root.data("crosshair",!root.data("crosshair")); root.select(".crosshair") .animate({opacity: root.data("crosshair") ? 1.0 : 0.0}, 250); } }; root.data("keyboard_pan_zoom", keyboard_pan_zoom); window.addEventListener("keyup", keyboard_pan_zoom); var xgridlines = root.select(".xgridlines"), ygridlines = root.select(".ygridlines"); if (xgridlines) { xgridlines.data("unfocused_strokedash", xgridlines.attribute("stroke-dasharray").replace(/(\d)(,|$)/g, "$1mm$2")); var destcolor = root.data("focused_xgrid_color"); xgridlines.attribute("stroke-dasharray", "none") .selectAll("path") .animate({stroke: destcolor}, 250); } if (ygridlines) { ygridlines.data("unfocused_strokedash", ygridlines.attribute("stroke-dasharray").replace(/(\d)(,|$)/g, "$1mm$2")); var destcolor = root.data("focused_ygrid_color"); ygridlines.attribute("stroke-dasharray", "none") .selectAll("path") .animate({stroke: destcolor}, 250); } root.select(".crosshair") .animate({opacity: root.data("crosshair") ? 1.0 : 0.0}, 250); root.select(".questionmark").animate({opacity: 1.0}, 250); }; // Reset pan and zoom on double click Gadfly.plot_dblclick = function(event) { set_plot_pan_zoom(this.plotroot(), 0.0, 0.0, 1.0, 1.0); }; // Unemphasize grid lines on mouse out. Gadfly.plot_mouseout = function(event) { var root = this.plotroot(); window.removeEventListener("keyup", root.data("keyboard_pan_zoom")); root.data("keyboard_pan_zoom", undefined); window.removeEventListener("keydown", root.data("keyboard_help")); root.data("keyboard_help", undefined); var xgridlines = root.select(".xgridlines"), ygridlines = root.select(".ygridlines"); if (xgridlines) { var destcolor = root.data("unfocused_xgrid_color"); xgridlines.attribute("stroke-dasharray", xgridlines.data("unfocused_strokedash")) .selectAll("path") .animate({stroke: destcolor}, 250); } if (ygridlines) { var destcolor = root.data("unfocused_ygrid_color"); ygridlines.attribute("stroke-dasharray", ygridlines.data("unfocused_strokedash")) .selectAll("path") .animate({stroke: destcolor}, 250); } root.select(".crosshair").animate({opacity: 0.0}, 250); root.select(".questionmark").animate({opacity: 0.0}, 250); helpscreen_hidden(root); }; var set_geometry_transform = function(root, tx, ty, xscale, yscale) { var xscalable = root.hasClass("xscalable"), yscalable = root.hasClass("yscalable"); var old_xscale = root.data("xscale"), old_yscale = root.data("yscale"); var xscale = xscalable ? xscale : 1.0, yscale = yscalable ? yscale : 1.0; tx = xscalable ? tx : 0.0; ty = yscalable ? ty : 0.0; var t = new Snap.Matrix().translate(tx, ty).scale(xscale, yscale); root.selectAll(".geometry, image").forEach(function (element, i) { element.transform(t); }); var t = new Snap.Matrix().scale(1.0/xscale, 1.0/yscale); root.selectAll('.marker').forEach(function (element, i) { element.selectAll('.primitive').forEach(function (element, i) { element.transform(t); }) }); bounds = root.plotbounds(); px_per_mm = root.data("px_per_mm"); if (yscalable) { var xfixed_t = new Snap.Matrix().translate(0, ty).scale(1.0, yscale); root.selectAll(".xfixed") .forEach(function (element, i) { element.transform(xfixed_t); }); ylabels = root.select(".ylabels"); if (ylabels) { ylabels.transform(xfixed_t) .selectAll("g") .forEach(function (element, i) { if (element.attribute("gadfly:inscale") == "true") { unscale_t = new Snap.Matrix(); unscale_t.scale(1, 1/yscale); element.select("text").transform(unscale_t); var y = element.attr("transform").globalMatrix.f / px_per_mm; element.attr("visibility", bounds.y0 &lt;= y &amp;&amp; y &lt;= bounds.y1 ? "visible" : "hidden"); } }); } } if (xscalable) { var yfixed_t = new Snap.Matrix().translate(tx, 0).scale(xscale, 1.0); var xtrans = new Snap.Matrix().translate(tx, 0); root.selectAll(".yfixed") .forEach(function (element, i) { element.transform(yfixed_t); }); xlabels = root.select(".xlabels"); if (xlabels) { xlabels.transform(yfixed_t) .selectAll("g") .forEach(function (element, i) { if (element.attribute("gadfly:inscale") == "true") { unscale_t = new Snap.Matrix(); unscale_t.scale(1/xscale, 1); element.select("text").transform(unscale_t); var x = element.attr("transform").globalMatrix.e / px_per_mm; element.attr("visibility", bounds.x0 &lt;= x &amp;&amp; x &lt;= bounds.x1 ? "visible" : "hidden"); } }); } } }; // Find the most appropriate tick scale and update label visibility. var update_tickscale = function(root, scale, axis) { if (!root.hasClass(axis + "scalable")) return; var tickscales = root.data(axis + "tickscales"); var best_tickscale = 1.0; var best_tickscale_dist = Infinity; for (tickscale in tickscales) { var dist = Math.abs(Math.log(tickscale) - Math.log(scale)); if (dist &lt; best_tickscale_dist) { best_tickscale_dist = dist; best_tickscale = tickscale; } } if (best_tickscale != root.data(axis + "tickscale")) { root.data(axis + "tickscale", best_tickscale); var mark_inscale_gridlines = function (element, i) { if (element.attribute("gadfly:inscale") == null) { return; } var inscale = element.attr("gadfly:scale") == best_tickscale; element.attribute("gadfly:inscale", inscale); element.attr("visibility", inscale ? "visible" : "hidden"); }; var mark_inscale_labels = function (element, i) { if (element.attribute("gadfly:inscale") == null) { return; } var inscale = element.attr("gadfly:scale") == best_tickscale; element.attribute("gadfly:inscale", inscale); element.attr("visibility", inscale ? "visible" : "hidden"); }; root.select("." + axis + "gridlines").selectAll("g").forEach(mark_inscale_gridlines); root.select("." + axis + "labels").selectAll("g").forEach(mark_inscale_labels); } }; var set_plot_pan_zoom = function(root, tx, ty, xscale, yscale) { var old_xscale = root.data("xscale"), old_yscale = root.data("yscale"); var bounds = root.plotbounds(); var width = bounds.x1 - bounds.x0, height = bounds.y1 - bounds.y0; // compute the viewport derived from tx, ty, xscale, and yscale var x_min = -width * xscale - (xscale * width - width), x_max = width * xscale, y_min = -height * yscale - (yscale * height - height), y_max = height * yscale; var x0 = bounds.x0 - xscale * bounds.x0, y0 = bounds.y0 - yscale * bounds.y0; var tx = Math.max(Math.min(tx - x0, x_max), x_min), ty = Math.max(Math.min(ty - y0, y_max), y_min); tx += x0; ty += y0; // when the scale changes, we may need to alter which set of // ticks are being displayed if (xscale != old_xscale) { update_tickscale(root, xscale, "x"); } if (yscale != old_yscale) { update_tickscale(root, yscale, "y"); } set_geometry_transform(root, tx, ty, xscale, yscale); root.data("xscale", xscale); root.data("yscale", yscale); root.data("tx", tx); root.data("ty", ty); }; var scale_centered_translation = function(root, xscale, yscale) { var bounds = root.plotbounds(); var width = bounds.x1 - bounds.x0, height = bounds.y1 - bounds.y0; var tx0 = root.data("tx"), ty0 = root.data("ty"); var xscale0 = root.data("xscale"), yscale0 = root.data("yscale"); // how off from center the current view is var xoff = tx0 - (bounds.x0 * (1 - xscale0) + (width * (1 - xscale0)) / 2), yoff = ty0 - (bounds.y0 * (1 - yscale0) + (height * (1 - yscale0)) / 2); // rescale offsets xoff = xoff * xscale / xscale0; yoff = yoff * yscale / yscale0; // adjust for the panel position being scaled var x_edge_adjust = bounds.x0 * (1 - xscale), y_edge_adjust = bounds.y0 * (1 - yscale); return { x: xoff + x_edge_adjust + (width - width * xscale) / 2, y: yoff + y_edge_adjust + (height - height * yscale) / 2 }; }; // Initialize data for panning zooming if it isn't already. var init_pan_zoom = function(root) { if (root.data("zoompan-ready")) { return; } root.data("crosshair",false); // The non-scaling-stroke trick. Rather than try to correct for the // stroke-width when zooming, we force it to a fixed value. var px_per_mm = root.node.getCTM().a; // Drag events report deltas in pixels, which we'd like to convert to // millimeters. root.data("px_per_mm", px_per_mm); root.selectAll("path") .forEach(function (element, i) { sw = element.asPX("stroke-width") * px_per_mm; if (sw &gt; 0) { element.attribute("stroke-width", sw); element.attribute("vector-effect", "non-scaling-stroke"); } }); // Store ticks labels original tranformation root.selectAll(".xlabels &gt; g, .ylabels &gt; g") .forEach(function (element, i) { var lm = element.transform().localMatrix; element.data("static_transform", new Snap.Matrix(lm.a, lm.b, lm.c, lm.d, lm.e, lm.f)); }); var xgridlines = root.select(".xgridlines"); var ygridlines = root.select(".ygridlines"); var xlabels = root.select(".xlabels"); var ylabels = root.select(".ylabels"); if (root.data("tx") === undefined) root.data("tx", 0); if (root.data("ty") === undefined) root.data("ty", 0); if (root.data("xscale") === undefined) root.data("xscale", 1.0); if (root.data("yscale") === undefined) root.data("yscale", 1.0); if (root.data("xtickscales") === undefined) { // index all the tick scales that are listed var xtickscales = {}; var ytickscales = {}; var add_x_tick_scales = function (element, i) { if (element.attribute("gadfly:scale")==null) { return; } xtickscales[element.attribute("gadfly:scale")] = true; }; var add_y_tick_scales = function (element, i) { if (element.attribute("gadfly:scale")==null) { return; } ytickscales[element.attribute("gadfly:scale")] = true; }; if (xgridlines) xgridlines.selectAll("g").forEach(add_x_tick_scales); if (ygridlines) ygridlines.selectAll("g").forEach(add_y_tick_scales); if (xlabels) xlabels.selectAll("g").forEach(add_x_tick_scales); if (ylabels) ylabels.selectAll("g").forEach(add_y_tick_scales); root.data("xtickscales", xtickscales); root.data("ytickscales", ytickscales); root.data("xtickscale", 1.0); root.data("ytickscale", 1.0); // ??? } var min_scale = 1.0, max_scale = 1.0; for (scale in xtickscales) { min_scale = Math.min(min_scale, scale); max_scale = Math.max(max_scale, scale); } for (scale in ytickscales) { min_scale = Math.min(min_scale, scale); max_scale = Math.max(max_scale, scale); } root.data("min_scale", min_scale); root.data("max_scale", max_scale); // store the original positions of labels if (xlabels) { xlabels.selectAll("g") .forEach(function (element, i) { element.data("x", element.asPX("x")); }); } if (ylabels) { ylabels.selectAll("g") .forEach(function (element, i) { element.data("y", element.asPX("y")); }); } // mark grid lines and ticks as in or out of scale. var mark_inscale = function (element, i) { if (element.attribute("gadfly:scale") == null) { return; } element.attribute("gadfly:inscale", element.attribute("gadfly:scale") == 1.0); }; if (xgridlines) xgridlines.selectAll("g").forEach(mark_inscale); if (ygridlines) ygridlines.selectAll("g").forEach(mark_inscale); if (xlabels) xlabels.selectAll("g").forEach(mark_inscale); if (ylabels) ylabels.selectAll("g").forEach(mark_inscale); // figure out the upper ond lower bounds on panning using the maximum // and minum grid lines var bounds = root.plotbounds(); var pan_bounds = { x0: 0.0, y0: 0.0, x1: 0.0, y1: 0.0 }; if (xgridlines) { xgridlines .selectAll("g") .forEach(function (element, i) { if (element.attribute("gadfly:inscale") == "true") { var bbox = element.node.getBBox(); if (bounds.x1 - bbox.x &lt; pan_bounds.x0) { pan_bounds.x0 = bounds.x1 - bbox.x; } if (bounds.x0 - bbox.x &gt; pan_bounds.x1) { pan_bounds.x1 = bounds.x0 - bbox.x; } element.attr("visibility", "visible"); } }); } if (ygridlines) { ygridlines .selectAll("g") .forEach(function (element, i) { if (element.attribute("gadfly:inscale") == "true") { var bbox = element.node.getBBox(); if (bounds.y1 - bbox.y &lt; pan_bounds.y0) { pan_bounds.y0 = bounds.y1 - bbox.y; } if (bounds.y0 - bbox.y &gt; pan_bounds.y1) { pan_bounds.y1 = bounds.y0 - bbox.y; } element.attr("visibility", "visible"); } }); } // nudge these values a little pan_bounds.x0 -= 5; pan_bounds.x1 += 5; pan_bounds.y0 -= 5; pan_bounds.y1 += 5; root.data("pan_bounds", pan_bounds); root.data("zoompan-ready", true) }; // drag actions, i.e. zooming and panning var pan_action = { start: function(root, x, y, event) { root.data("dx", 0); root.data("dy", 0); root.data("tx0", root.data("tx")); root.data("ty0", root.data("ty")); }, update: function(root, dx, dy, x, y, event) { var px_per_mm = root.data("px_per_mm"); dx /= px_per_mm; dy /= px_per_mm; var tx0 = root.data("tx"), ty0 = root.data("ty"); var dx0 = root.data("dx"), dy0 = root.data("dy"); root.data("dx", dx); root.data("dy", dy); dx = dx - dx0; dy = dy - dy0; var tx = tx0 + dx, ty = ty0 + dy; set_plot_pan_zoom(root, tx, ty, root.data("xscale"), root.data("yscale")); }, end: function(root, event) { }, cancel: function(root) { set_plot_pan_zoom(root, root.data("tx0"), root.data("ty0"), root.data("xscale"), root.data("yscale")); } }; var zoom_box; var zoom_action = { start: function(root, x, y, event) { var bounds = root.plotbounds(); var width = bounds.x1 - bounds.x0, height = bounds.y1 - bounds.y0; var xscalable = root.hasClass("xscalable"), yscalable = root.hasClass("yscalable"); var px_per_mm = root.data("px_per_mm"); x = xscalable ? x / px_per_mm : bounds.x0; y = yscalable ? y / px_per_mm : bounds.y0; var w = xscalable ? 0 : width; var h = yscalable ? 0 : height; zoom_box = root.rect(x, y, w, h).attr({ "fill": "#000", "opacity": 0.25 }); }, update: function(root, dx, dy, x, y, event) { var xscalable = root.hasClass("xscalable"), yscalable = root.hasClass("yscalable"); var px_per_mm = root.data("px_per_mm"); var bounds = root.plotbounds(); if (yscalable) { y /= px_per_mm; y = Math.max(bounds.y0, y); y = Math.min(bounds.y1, y); } else { y = bounds.y1; } if (xscalable) { x /= px_per_mm; x = Math.max(bounds.x0, x); x = Math.min(bounds.x1, x); } else { x = bounds.x1; } dx = x - zoom_box.attr("x"); dy = y - zoom_box.attr("y"); var xoffset = 0, yoffset = 0; if (dx &lt; 0) { xoffset = dx; dx = -1 * dx; } if (dy &lt; 0) { yoffset = dy; dy = -1 * dy; } if (isNaN(dy)) { dy = 0.0; } if (isNaN(dx)) { dx = 0.0; } zoom_box.transform("T" + xoffset + "," + yoffset); zoom_box.attr("width", dx); zoom_box.attr("height", dy); }, end: function(root, event) { var xscalable = root.hasClass("xscalable"), yscalable = root.hasClass("yscalable"); var zoom_bounds = zoom_box.getBBox(); if (zoom_bounds.width * zoom_bounds.height &lt;= 0) { return; } var plot_bounds = root.plotbounds(); var xzoom_factor = 1.0, yzoom_factor = 1.0; if (xscalable) { xzoom_factor = (plot_bounds.x1 - plot_bounds.x0) / zoom_bounds.width; } if (yscalable) { yzoom_factor = (plot_bounds.y1 - plot_bounds.y0) / zoom_bounds.height; } var tx = (root.data("tx") - zoom_bounds.x) * xzoom_factor + plot_bounds.x0, ty = (root.data("ty") - zoom_bounds.y) * yzoom_factor + plot_bounds.y0; set_plot_pan_zoom(root, tx, ty, root.data("xscale") * xzoom_factor, root.data("yscale") * yzoom_factor); zoom_box.remove(); }, cancel: function(root) { zoom_box.remove(); } }; Gadfly.guide_background_drag_onstart = function(x, y, event) { var root = this.plotroot(); var scalable = root.hasClass("xscalable") || root.hasClass("yscalable"); var zoomable = !event.altKey &amp;&amp; !event.ctrlKey &amp;&amp; event.shiftKey &amp;&amp; scalable; var panable = !event.altKey &amp;&amp; !event.ctrlKey &amp;&amp; !event.shiftKey &amp;&amp; scalable; var drag_action = zoomable ? zoom_action : panable ? pan_action : undefined; root.data("drag_action", drag_action); if (drag_action) { var cancel_drag_action = function(event) { if (event.which == 27) { // esc key drag_action.cancel(root); root.data("drag_action", undefined); } }; window.addEventListener("keyup", cancel_drag_action); root.data("cancel_drag_action", cancel_drag_action); drag_action.start(root, x, y, event); } }; Gadfly.guide_background_drag_onmove = function(dx, dy, x, y, event) { var root = this.plotroot(); var drag_action = root.data("drag_action"); if (drag_action) { drag_action.update(root, dx, dy, x, y, event); } }; Gadfly.guide_background_drag_onend = function(event) { var root = this.plotroot(); window.removeEventListener("keyup", root.data("cancel_drag_action")); root.data("cancel_drag_action", undefined); var drag_action = root.data("drag_action"); if (drag_action) { drag_action.end(root, event); } root.data("drag_action", undefined); }; Gadfly.guide_background_scroll = function(event) { if (event.shiftKey) { increase_zoom_by_position(this.plotroot(), 0.001 * event.wheelDelta); event.preventDefault(); } }; // Map slider position x to scale y using the function y = a*exp(b*x)+c. // The constants a, b, and c are solved using the constraint that the function // should go through the points (0; min_scale), (0.5; 1), and (1; max_scale). var scale_from_slider_position = function(position, min_scale, max_scale) { if (min_scale==max_scale) { return 1; } var a = (1 - 2 * min_scale + min_scale * min_scale) / (min_scale + max_scale - 2), b = 2 * Math.log((max_scale - 1) / (1 - min_scale)), c = (min_scale * max_scale - 1) / (min_scale + max_scale - 2); return a * Math.exp(b * position) + c; } // inverse of scale_from_slider_position var slider_position_from_scale = function(scale, min_scale, max_scale) { if (min_scale==max_scale) { return min_scale; } var a = (1 - 2 * min_scale + min_scale * min_scale) / (min_scale + max_scale - 2), b = 2 * Math.log((max_scale - 1) / (1 - min_scale)), c = (min_scale * max_scale - 1) / (min_scale + max_scale - 2); return 1 / b * Math.log((scale - c) / a); } var increase_zoom_by_position = function(root, delta_position, animate) { var old_xscale = root.data("xscale"), old_yscale = root.data("yscale"), min_scale = root.data("min_scale"), max_scale = root.data("max_scale"); var xposition = slider_position_from_scale(old_xscale, min_scale, max_scale), yposition = slider_position_from_scale(old_yscale, min_scale, max_scale); xposition += (root.hasClass("xscalable") ? delta_position : 0.0); yposition += (root.hasClass("yscalable") ? delta_position : 0.0); old_xscale = scale_from_slider_position(xposition, min_scale, max_scale); old_yscale = scale_from_slider_position(yposition, min_scale, max_scale); var new_xscale = Math.max(min_scale, Math.min(old_xscale, max_scale)), new_yscale = Math.max(min_scale, Math.min(old_yscale, max_scale)); if (animate) { Snap.animate( [old_xscale, old_yscale], [new_xscale, new_yscale], function (new_scale) { update_plot_scale(root, new_scale[0], new_scale[1]); }, 200); } else { update_plot_scale(root, new_xscale, new_yscale); } } var update_plot_scale = function(root, new_xscale, new_yscale) { var trans = scale_centered_translation(root, new_xscale, new_yscale); set_plot_pan_zoom(root, trans.x, trans.y, new_xscale, new_yscale); }; var toggle_color_class = function(root, color_class, ison) { var escaped_color_class = color_class.replace(/([^0-9a-zA-z])/g,"\\$1"); var guides = root.selectAll(".guide." + escaped_color_class + ",.guide ." + escaped_color_class); var geoms = root.selectAll(".geometry." + escaped_color_class + ",.geometry ." + escaped_color_class); if (ison) { guides.animate({opacity: 0.5}, 250); geoms.animate({opacity: 0.0}, 250); } else { guides.animate({opacity: 1.0}, 250); geoms.animate({opacity: 1.0}, 250); } }; Gadfly.colorkey_swatch_click = function(event) { var root = this.plotroot(); var color_class = this.data("color_class"); if (event.shiftKey) { root.selectAll(".colorkey g") .forEach(function (element) { var other_color_class = element.data("color_class"); if (typeof other_color_class !== 'undefined' &amp;&amp; other_color_class != color_class) { toggle_color_class(root, other_color_class, element.attr("opacity") == 1.0); } }); } else { toggle_color_class(root, color_class, this.attr("opacity") == 1.0); } }; return Gadfly; })); //@ sourceURL=gadfly.js (function (glob, factory) { // AMD support if (typeof require === "function" &amp;&amp; typeof define === "function" &amp;&amp; define.amd) { require(["Snap.svg", "Gadfly"], function (Snap, Gadfly) { factory(Snap, Gadfly); }); } else { factory(glob.Snap, glob.Gadfly); } })(window, function (Snap, Gadfly) { var fig = Snap("#img-3d696918"); fig.select("#img-3d696918-5") .init_gadfly(); fig.select("#img-3d696918-8") .plotroot().data("unfocused_ygrid_color", "#D0D0E0") ; fig.select("#img-3d696918-8") .plotroot().data("focused_ygrid_color", "#A0A0A0") ; fig.select("#img-3d696918-114") .plotroot().data("unfocused_xgrid_color", "#D0D0E0") ; fig.select("#img-3d696918-114") .plotroot().data("focused_xgrid_color", "#A0A0A0") ; fig.select("#img-3d696918-560") .mouseenter(Gadfly.helpscreen_visible) .mouseleave(Gadfly.helpscreen_hidden) ; }); // ]]></script> </p> <p> </p> <p><strong>ソース実行結果!!!!  </strong></p> <pre style="box-sizing: border-box; overflow: auto; font-family: monospace; font-size: 14px; display: block; padding: 1px 0px; margin: 0px; line-height: inherit; word-break: break-all; overflow-wrap: break-word; color: #000000; background-color: #ffffff; border: 0px; border-radius: 0px; white-space: pre-wrap; vertical-align: baseline; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Coefficients: Estimate Std.Error t <a class="keyword" href="http://d.hatena.ne.jp/keyword/value">value</a> Pr(&gt;|t|) (Intercept) 257.544 90.8531 2.83473 0.0056 height -1.30983 0.574484 -2.28 0.0248</pre> <p><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:gadfly="http://www.gadflyjl.org/ns" version="1.2" width="141.42mm" height="100mm" viewbox="0 0 141.42 100" stroke="none" fill="#000000" stroke-width="0.3" font-size="3.88"> <defs> <marker id="arrow" markerwidth="15" markerheight="7" refx="5" refy="3.5" orient="auto" markerunits="strokeWidth"> <path d="M0,0 L15,3.5 L0,7 z" stroke="context-stroke" fill="context-stroke"></path> </marker> </defs> <g class="plotroot xscalable yscalable" id="img-c9da9a49-1"> <g font-size="3.88" font-family="'PT Sans','Helvetica Neue','Helvetica',sans-serif" fill="#564A55" stroke="#000000" stroke-opacity="0.000" id="img-c9da9a49-2"> <g transform="translate(78.03,88.39)"> <g class="primitive"> <text text-anchor="middle" dy="0.6em">height</text> </g> </g> </g> <g class="guide xlabels" font-size="2.82" font-family="'PT Sans Caption','Helvetica Neue','Helvetica',sans-serif" fill="#6C606B" id="img-c9da9a49-3"> <g transform="translate(21.63,81.72)"> <g class="primitive"> <text text-anchor="middle" dy="0.6em">140</text> </g> </g> <g transform="translate(49.83,81.72)"> <g class="primitive"> <text text-anchor="middle" dy="0.6em">150</text> </g> </g> <g transform="translate(78.03,81.72)"> <g class="primitive"> <text text-anchor="middle" dy="0.6em">160</text> </g> </g> <g transform="translate(106.22,81.72)"> <g class="primitive"> <text text-anchor="middle" dy="0.6em">170</text> </g> </g> <g transform="translate(134.42,81.72)"> <g class="primitive"> <text text-anchor="middle" dy="0.6em">180</text> </g> </g> </g> <g clip-path="url(#img-c9da9a49-4)"> <g id="img-c9da9a49-5"> <g pointer-events="visible" fill="#000000" fill-opacity="0.000" stroke="#000000" stroke-opacity="0.000" class="guide background" id="img-c9da9a49-6"> <g transform="translate(78.03,45.66)" id="img-c9da9a49-7"> <path d="M-58.39,-35.05 L 58.39 -35.05 58.39 35.05 -58.39 35.05 z" class="primitive"></path> </g> </g> <g class="guide ygridlines xfixed" stroke-dasharray="0.5,0.5" stroke-width="0.2" stroke="#D0D0E0" id="img-c9da9a49-8"> <g transform="translate(78.03,78.72)" id="img-c9da9a49-9"> <path fill="none" d="M-58.39,0 L 58.39 0" class="primitive"></path> </g> <g transform="translate(78.03,45.66)" id="img-c9da9a49-10"> <path fill="none" d="M-58.39,0 L 58.39 0" class="primitive"></path> </g> <g transform="translate(78.03,12.61)" id="img-c9da9a49-11"> <path fill="none" d="M-58.39,0 L 58.39 0" class="primitive"></path> </g> </g> <g class="guide xgridlines yfixed" stroke-dasharray="0.5,0.5" stroke-width="0.2" stroke="#D0D0E0" id="img-c9da9a49-12"> <g transform="translate(21.63,45.66)" id="img-c9da9a49-13"> <path fill="none" d="M0,-35.05 L 0 35.05" class="primitive"></path> </g> <g transform="translate(49.83,45.66)" id="img-c9da9a49-14"> <path fill="none" d="M0,-35.05 L 0 35.05" class="primitive"></path> </g> <g transform="translate(78.03,45.66)" id="img-c9da9a49-15"> <path fill="none" d="M0,-35.05 L 0 35.05" class="primitive"></path> </g> <g transform="translate(106.22,45.66)" id="img-c9da9a49-16"> <path fill="none" d="M0,-35.05 L 0 35.05" class="primitive"></path> </g> <g transform="translate(134.42,45.66)" id="img-c9da9a49-17"> <path fill="none" d="M0,-35.05 L 0 35.05" class="primitive"></path> </g> </g> <g class="plotpanel" id="img-c9da9a49-18"> <metadata> <boundingbox value="19.63166666666666mm 10.611666666666665mm 116.78968957064285mm 70.10333333333334mm"></boundingbox> <unitbox value="139.2907153100205 103.02556603297867 41.418569379959045 -106.05113206595733"></unitbox> </metadata> <g stroke-width="0.79" fill="#000000" fill-opacity="0.000" class="geometry" id="img-c9da9a49-19"> <g class="color_RGB{N0f8}(0.0,0.502,0.0)" stroke-dasharray="none" stroke="#008000" id="img-c9da9a49-20"> <g transform="translate(78.03,47)" id="img-c9da9a49-21"> <path fill="none" d="M-56.39,-17.32 L -53.58 -16.45 -50.76 -15.59 -47.94 -14.72 -45.12 -13.85 -42.3 -12.99 -39.48 -12.12 -36.66 -11.26 -33.84 -10.39 -31.02 -9.52 -28.2 -8.66 -25.38 -7.79 -22.56 -6.93 -19.74 -6.06 -16.92 -5.2 -14.1 -4.33 -11.28 -3.46 -8.46 -2.6 -5.64 -1.73 -2.82 -0.87 0 0 2.82 0.87 5.64 1.73 8.46 2.6 11.28 3.46 14.1 4.33 16.92 5.2 19.74 6.06 22.56 6.93 25.38 7.79 28.2 8.66 31.02 9.52 33.84 10.39 36.66 11.26 39.48 12.12 42.3 12.99 45.12 13.85 47.94 14.72 50.76 15.59 53.58 16.45 56.39 17.32" class="primitive"></path> </g> </g> </g> <g class="geometry" id="img-c9da9a49-22"> <g stroke-width="0.3" id="img-c9da9a49-23"> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-24"> <g class="marker" id="img-c9da9a49-25"> <g transform="translate(72.39,12.61)" id="img-c9da9a49-26"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-27"> <g class="marker" id="img-c9da9a49-28"> <g transform="translate(75.21,13.27)" id="img-c9da9a49-29"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-30"> <g class="marker" id="img-c9da9a49-31"> <g transform="translate(83.67,13.93)" id="img-c9da9a49-32"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-33"> <g class="marker" id="img-c9da9a49-34"> <g transform="translate(92.13,14.59)" id="img-c9da9a49-35"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-36"> <g class="marker" id="img-c9da9a49-37"> <g transform="translate(58.29,15.26)" id="img-c9da9a49-38"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-39"> <g class="marker" id="img-c9da9a49-40"> <g transform="translate(27.27,15.92)" id="img-c9da9a49-41"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-42"> <g class="marker" id="img-c9da9a49-43"> <g transform="translate(72.39,16.58)" id="img-c9da9a49-44"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-45"> <g class="marker" id="img-c9da9a49-46"> <g transform="translate(55.47,17.24)" id="img-c9da9a49-47"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-48"> <g class="marker" id="img-c9da9a49-49"> <g transform="translate(83.67,17.9)" id="img-c9da9a49-50"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-51"> <g class="marker" id="img-c9da9a49-52"> <g transform="translate(75.21,18.56)" id="img-c9da9a49-53"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-54"> <g class="marker" id="img-c9da9a49-55"> <g transform="translate(66.75,19.22)" id="img-c9da9a49-56"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-57"> <g class="marker" id="img-c9da9a49-58"> <g transform="translate(72.39,19.88)" id="img-c9da9a49-59"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-60"> <g class="marker" id="img-c9da9a49-61"> <g transform="translate(38.55,20.54)" id="img-c9da9a49-62"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-63"> <g class="marker" id="img-c9da9a49-64"> <g transform="translate(97.76,21.21)" id="img-c9da9a49-65"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-66"> <g class="marker" id="img-c9da9a49-67"> <g transform="translate(49.83,21.87)" id="img-c9da9a49-68"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-69"> <g class="marker" id="img-c9da9a49-70"> <g transform="translate(58.29,22.53)" id="img-c9da9a49-71"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-72"> <g class="marker" id="img-c9da9a49-73"> <g transform="translate(72.39,23.19)" id="img-c9da9a49-74"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-75"> <g class="marker" id="img-c9da9a49-76"> <g transform="translate(55.47,23.85)" id="img-c9da9a49-77"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-78"> <g class="marker" id="img-c9da9a49-79"> <g transform="translate(58.29,24.51)" id="img-c9da9a49-80"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-81"> <g class="marker" id="img-c9da9a49-82"> <g transform="translate(80.85,25.17)" id="img-c9da9a49-83"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-84"> <g class="marker" id="img-c9da9a49-85"> <g transform="translate(58.29,25.83)" id="img-c9da9a49-86"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-87"> <g class="marker" id="img-c9da9a49-88"> <g transform="translate(75.21,26.49)" id="img-c9da9a49-89"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-90"> <g class="marker" id="img-c9da9a49-91"> <g transform="translate(55.47,27.15)" id="img-c9da9a49-92"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-93"> <g class="marker" id="img-c9da9a49-94"> <g transform="translate(55.47,27.82)" id="img-c9da9a49-95"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-96"> <g class="marker" id="img-c9da9a49-97"> <g transform="translate(72.39,28.48)" id="img-c9da9a49-98"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-99"> <g class="marker" id="img-c9da9a49-100"> <g transform="translate(58.29,29.14)" id="img-c9da9a49-101"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-102"> <g class="marker" id="img-c9da9a49-103"> <g transform="translate(63.93,29.8)" id="img-c9da9a49-104"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-105"> <g class="marker" id="img-c9da9a49-106"> <g transform="translate(66.75,30.46)" id="img-c9da9a49-107"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-108"> <g class="marker" id="img-c9da9a49-109"> <g transform="translate(66.75,31.12)" id="img-c9da9a49-110"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-111"> <g class="marker" id="img-c9da9a49-112"> <g transform="translate(86.49,31.78)" id="img-c9da9a49-113"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-114"> <g class="marker" id="img-c9da9a49-115"> <g transform="translate(78.03,32.44)" id="img-c9da9a49-116"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-117"> <g class="marker" id="img-c9da9a49-118"> <g transform="translate(72.39,33.1)" id="img-c9da9a49-119"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-120"> <g class="marker" id="img-c9da9a49-121"> <g transform="translate(72.39,33.76)" id="img-c9da9a49-122"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-123"> <g class="marker" id="img-c9da9a49-124"> <g transform="translate(63.93,34.43)" id="img-c9da9a49-125"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-126"> <g class="marker" id="img-c9da9a49-127"> <g transform="translate(69.57,35.09)" id="img-c9da9a49-128"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-129"> <g class="marker" id="img-c9da9a49-130"> <g transform="translate(49.83,35.75)" id="img-c9da9a49-131"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-132"> <g class="marker" id="img-c9da9a49-133"> <g transform="translate(92.13,36.41)" id="img-c9da9a49-134"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-135"> <g class="marker" id="img-c9da9a49-136"> <g transform="translate(72.39,37.07)" id="img-c9da9a49-137"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-138"> <g class="marker" id="img-c9da9a49-139"> <g transform="translate(83.67,37.73)" id="img-c9da9a49-140"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-141"> <g class="marker" id="img-c9da9a49-142"> <g transform="translate(63.93,38.39)" id="img-c9da9a49-143"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-144"> <g class="marker" id="img-c9da9a49-145"> <g transform="translate(69.57,39.05)" id="img-c9da9a49-146"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-147"> <g class="marker" id="img-c9da9a49-148"> <g transform="translate(83.67,39.71)" id="img-c9da9a49-149"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-150"> <g class="marker" id="img-c9da9a49-151"> <g transform="translate(72.39,40.38)" id="img-c9da9a49-152"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-153"> <g class="marker" id="img-c9da9a49-154"> <g transform="translate(89.31,41.04)" id="img-c9da9a49-155"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-156"> <g class="marker" id="img-c9da9a49-157"> <g transform="translate(78.03,41.7)" id="img-c9da9a49-158"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-159"> <g class="marker" id="img-c9da9a49-160"> <g transform="translate(106.22,42.36)" id="img-c9da9a49-161"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-162"> <g class="marker" id="img-c9da9a49-163"> <g transform="translate(66.75,43.02)" id="img-c9da9a49-164"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-165"> <g class="marker" id="img-c9da9a49-166"> <g transform="translate(55.47,43.68)" id="img-c9da9a49-167"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-168"> <g class="marker" id="img-c9da9a49-169"> <g transform="translate(78.03,44.34)" id="img-c9da9a49-170"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-171"> <g class="marker" id="img-c9da9a49-172"> <g transform="translate(97.76,45)" id="img-c9da9a49-173"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-174"> <g class="marker" id="img-c9da9a49-175"> <g transform="translate(63.93,45.66)" id="img-c9da9a49-176"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-177"> <g class="marker" id="img-c9da9a49-178"> <g transform="translate(80.85,46.32)" id="img-c9da9a49-179"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-180"> <g class="marker" id="img-c9da9a49-181"> <g transform="translate(89.31,46.99)" id="img-c9da9a49-182"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-183"> <g class="marker" id="img-c9da9a49-184"> <g transform="translate(78.03,47.65)" id="img-c9da9a49-185"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-186"> <g class="marker" id="img-c9da9a49-187"> <g transform="translate(66.75,48.31)" id="img-c9da9a49-188"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-189"> <g class="marker" id="img-c9da9a49-190"> <g transform="translate(72.39,48.97)" id="img-c9da9a49-191"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-192"> <g class="marker" id="img-c9da9a49-193"> <g transform="translate(63.93,49.63)" id="img-c9da9a49-194"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-195"> <g class="marker" id="img-c9da9a49-196"> <g transform="translate(92.13,50.29)" id="img-c9da9a49-197"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-198"> <g class="marker" id="img-c9da9a49-199"> <g transform="translate(58.29,50.95)" id="img-c9da9a49-200"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-201"> <g class="marker" id="img-c9da9a49-202"> <g transform="translate(78.03,51.61)" id="img-c9da9a49-203"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-204"> <g class="marker" id="img-c9da9a49-205"> <g transform="translate(63.93,52.27)" id="img-c9da9a49-206"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-207"> <g class="marker" id="img-c9da9a49-208"> <g transform="translate(89.31,52.93)" id="img-c9da9a49-209"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-210"> <g class="marker" id="img-c9da9a49-211"> <g transform="translate(55.47,53.6)" id="img-c9da9a49-212"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-213"> <g class="marker" id="img-c9da9a49-214"> <g transform="translate(66.75,54.26)" id="img-c9da9a49-215"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-216"> <g class="marker" id="img-c9da9a49-217"> <g transform="translate(58.29,54.92)" id="img-c9da9a49-218"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-219"> <g class="marker" id="img-c9da9a49-220"> <g transform="translate(61.11,55.58)" id="img-c9da9a49-221"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-222"> <g class="marker" id="img-c9da9a49-223"> <g transform="translate(80.85,56.24)" id="img-c9da9a49-224"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-225"> <g class="marker" id="img-c9da9a49-226"> <g transform="translate(89.31,56.9)" id="img-c9da9a49-227"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-228"> <g class="marker" id="img-c9da9a49-229"> <g transform="translate(92.13,57.56)" id="img-c9da9a49-230"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-231"> <g class="marker" id="img-c9da9a49-232"> <g transform="translate(78.03,58.22)" id="img-c9da9a49-233"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-234"> <g class="marker" id="img-c9da9a49-235"> <g transform="translate(72.39,58.88)" id="img-c9da9a49-236"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-237"> <g class="marker" id="img-c9da9a49-238"> <g transform="translate(63.93,59.55)" id="img-c9da9a49-239"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-240"> <g class="marker" id="img-c9da9a49-241"> <g transform="translate(78.03,60.21)" id="img-c9da9a49-242"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-243"> <g class="marker" id="img-c9da9a49-244"> <g transform="translate(69.57,60.87)" id="img-c9da9a49-245"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-246"> <g class="marker" id="img-c9da9a49-247"> <g transform="translate(100.58,61.53)" id="img-c9da9a49-248"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-249"> <g class="marker" id="img-c9da9a49-250"> <g transform="translate(58.29,62.19)" id="img-c9da9a49-251"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-252"> <g class="marker" id="img-c9da9a49-253"> <g transform="translate(80.85,62.85)" id="img-c9da9a49-254"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-255"> <g class="marker" id="img-c9da9a49-256"> <g transform="translate(94.94,63.51)" id="img-c9da9a49-257"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-258"> <g class="marker" id="img-c9da9a49-259"> <g transform="translate(75.21,64.17)" id="img-c9da9a49-260"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-261"> <g class="marker" id="img-c9da9a49-262"> <g transform="translate(83.67,64.83)" id="img-c9da9a49-263"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-264"> <g class="marker" id="img-c9da9a49-265"> <g transform="translate(55.47,65.49)" id="img-c9da9a49-266"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-267"> <g class="marker" id="img-c9da9a49-268"> <g transform="translate(55.47,66.16)" id="img-c9da9a49-269"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-270"> <g class="marker" id="img-c9da9a49-271"> <g transform="translate(75.21,66.82)" id="img-c9da9a49-272"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-273"> <g class="marker" id="img-c9da9a49-274"> <g transform="translate(78.03,67.48)" id="img-c9da9a49-275"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-276"> <g class="marker" id="img-c9da9a49-277"> <g transform="translate(72.39,68.14)" id="img-c9da9a49-278"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-279"> <g class="marker" id="img-c9da9a49-280"> <g transform="translate(72.39,68.8)" id="img-c9da9a49-281"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-282"> <g class="marker" id="img-c9da9a49-283"> <g transform="translate(58.29,69.46)" id="img-c9da9a49-284"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-285"> <g class="marker" id="img-c9da9a49-286"> <g transform="translate(72.39,70.12)" id="img-c9da9a49-287"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-288"> <g class="marker" id="img-c9da9a49-289"> <g transform="translate(92.13,70.78)" id="img-c9da9a49-290"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-291"> <g class="marker" id="img-c9da9a49-292"> <g transform="translate(75.21,71.44)" id="img-c9da9a49-293"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-294"> <g class="marker" id="img-c9da9a49-295"> <g transform="translate(80.85,72.1)" id="img-c9da9a49-296"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-297"> <g class="marker" id="img-c9da9a49-298"> <g transform="translate(66.75,72.77)" id="img-c9da9a49-299"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-300"> <g class="marker" id="img-c9da9a49-301"> <g transform="translate(78.03,73.43)" id="img-c9da9a49-302"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-303"> <g class="marker" id="img-c9da9a49-304"> <g transform="translate(97.76,74.09)" id="img-c9da9a49-305"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-306"> <g class="marker" id="img-c9da9a49-307"> <g transform="translate(55.47,74.75)" id="img-c9da9a49-308"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-309"> <g class="marker" id="img-c9da9a49-310"> <g transform="translate(80.85,75.41)" id="img-c9da9a49-311"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-312"> <g class="marker" id="img-c9da9a49-313"> <g transform="translate(86.49,76.07)" id="img-c9da9a49-314"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-315"> <g class="marker" id="img-c9da9a49-316"> <g transform="translate(92.13,76.73)" id="img-c9da9a49-317"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-318"> <g class="marker" id="img-c9da9a49-319"> <g transform="translate(80.85,77.39)" id="img-c9da9a49-320"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> <g class="color_RGBA{Float32}(0.4666667f0,0.6f0,1.0f0,1.0f0)" stroke="#FFFFFF" fill="#7799FF" id="img-c9da9a49-321"> <g class="marker" id="img-c9da9a49-322"> <g transform="translate(55.47,78.05)" id="img-c9da9a49-323"> <circle cx="0" cy="0" r="0.9" class="primitive"></circle> </g> </g> </g> </g> </g> </g> </g> </g> <g class="guide ylabels" font-size="2.82" font-family="'PT Sans Caption','Helvetica Neue','Helvetica',sans-serif" fill="#6C606B" id="img-c9da9a49-324"> <g transform="translate(18.63,78.72)" id="img-c9da9a49-325"> <g class="primitive"> <text text-anchor="end" dy="0.35em">0</text> </g> </g> <g transform="translate(18.63,45.66)" id="img-c9da9a49-326"> <g class="primitive"> <text text-anchor="end" dy="0.35em">50</text> </g> </g> <g transform="translate(18.63,12.61)" id="img-c9da9a49-327"> <g class="primitive"> <text text-anchor="end" dy="0.35em">100</text> </g> </g> </g> <g font-size="3.88" font-family="'PT Sans','Helvetica Neue','Helvetica',sans-serif" fill="#564A55" stroke="#000000" stroke-opacity="0.000" id="img-c9da9a49-328"> <g transform="translate(8.81,43.66)" id="img-c9da9a49-329"> <g class="primitive"> <text text-anchor="middle" dy="0.35em" transform="rotate(-90,0, 2)">rank</text> </g> </g> </g> <g font-size="3.88" font-family="'PT Sans','Helvetica Neue','Helvetica',sans-serif" fill="#564A55" stroke="#000000" stroke-opacity="0.000" id="img-c9da9a49-330"> <g transform="translate(78.03,5)" id="img-c9da9a49-331"> <g class="primitive"> <text text-anchor="middle" dy="0.6em">Regression image</text> </g> </g> </g> </g> <defs> <clippath id="img-c9da9a49-4"> <path d="M19.63,10.61 L 136.42 10.61 136.42 80.72 19.63 80.72"></path> </clippath> </defs> </svg></p> <p> </p> <p>このように綺麗なグラフと検定結果を簡単に出力してくれました!</p> <p><span style="color: #333333; font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">実際に表示されるグラフはD3.jsなので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BF%A5%E9%A5%AF%A5%C6%A5%A3%A5%D6">インタラクティブ</a>に動かすことができます。</span></p> <p>JuliaとJupyter組み合わせバッチリですね!</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>一部省略して実行したので、データ分析が得意な方には目を瞑って頂けると幸いです、、、(すみません、、、)</p> <p><span style="text-decoration: underline; font-size: 80%;">ちなむと検定結果は、一応身長が高いほどランキングは1位に近くなります、、</span></p> <p> </p> <p>あまりドキュメントも出回っていない言語であるJuliaですが、これからも勉強しながらこのプロジェクトを成功させられるように頑張ります!</p> <p>数学も少しは勉強しないと、、</p> <p> </p> <p>このプロジェクトの結果は次回でご報告がきっとできればと思います!!</p> <h3 style="clear: both; margin: 1.3em 0px 0.8em; line-height: 1.5; font-size: 21px; color: #3f3f3f; font-family: -apple-system, system-ui, 'Helvetica Neue', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', '游ゴシック Medium', meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial;">最後に</h3> <p>ITプロパートナーズでは、Laravel、vue.js、Nuxt.jsで開発に挑戦したいエンジニア・デザイナーを絶賛募集中です!</p> <p>そして、21卒新卒エンジニアのエントリーも心よりお待ちしております!</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Laravel/Vue.js(Nuxt.js)で開発したいエンジニア募集! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F192998" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/192998">www.wantedly.com</a></cite></p> <p> </p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="<1人目UI/UXデザイナー求む>デザインに関わる全ての裁量権を任せます! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F83872" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/83872">www.wantedly.com</a></cite></p> <p> </p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="急成長スタートアップ企業で自社サービスを作る!! リードエンジニアを募集! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> marron-web-engineer 関西初開催!!#ProLabo(プロラボ)もくもく会@なんば 5月25日(土)に開催しました!  hatenablog://entry/17680117127171414754 2019-06-06T11:09:27+09:00 2019-06-06T11:09:27+09:00 みなさんこんにちは! ITプロパートナーズ(https://itpropartners.jp/)新卒2年目の藤井です! 今年の4月から関西支社立ち上げにあたり大阪に赴任、 現在立ち上げに関連する業務全般を行なっております! 実は先日、毎月渋谷で行なっている#ProLabo(プロラボ)もくもく会がなんと、、、 関西に進出いたしました~🎉🎉 今回はそちらの様子をレポートにまとめました!! ぜひご覧ください! <p class="p1">みなさんこんにちは!</p> <p class="p3"><span class="s1">IT</span><span class="s2">プロパートナーズ(<a href="https://itpropartners.jp/"><span class="s3">https://itpropartners.jp/</span></a>)新卒</span><span class="s1">2</span><span class="s2">年目の藤井です!</span></p> <p class="p1">今年の<span class="s4">4</span>月から関西支社立ち上げにあたり大阪に赴任、</p> <p class="p1">現在立ち上げに関連する業務全般を行なっております!</p> <p class="p1">実は先日、毎月渋谷で行なっている<span class="s4">#ProLabo</span>(プロラボ)<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%E2%A4%AF%A4%E2%A4%AF%B2%F1">もくもく会</a>がなんと、、、</p> <p> </p> <p class="p1"><strong>関西に進出いたしました~🎉🎉</strong></p> <p class="p2"> </p> <p class="p1">今回はそちらの様子をレポートにまとめました!!</p> <p class="p1">ぜひご覧ください!</p> <p> </p> <p class="p1"><span class="s1">5/25</span>(土)、なんば<span class="s1">WeWork</span>の会議室にて、</p> <p><a href="https://itpropartners.connpass.com/event/131578/">#ProLabo(プロラボ)なんばもくもく会(#駆け出しエンジニア or 初心者歓迎#1 - connpass</a></p> <p class="p1">を開催しました!!</p> <p class="p3"> </p> <p class="p1">これまで<span class="s1">IT</span>プロ本社のある渋谷では毎月開催をしておりましたが、</p> <p class="p1">『関西を盛り上げる』べくやるべきことの一つとして、</p> <p class="p1">学べたり仲間を作ったりできるコミュニティは絶対に必要であると考えていたので、</p> <p class="p1">この度関西でも開催する運びとなりました。</p> <p class="p3"> </p> <p class="p1"><span class="s3">※</span>これまでの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%E2%A4%AF%A4%E2%A4%AF%B2%F1">もくもく会</a>の様子はこちら<span class="s1">↓</span></p> <p><a href="https://tech.itpropartners.jp/entry/2019/01/17/235205">毎月恒例!#ProLabo(プロラボ)渋谷もくもく会@渋谷1月12日(土)に開催しました! - ITPROPARTNERS Tech Blog| 株式会社ITプロパートナーズ</a></p> <p class="p3"> </p> <p class="p1">関西で初開催ということで、</p> <p class="p1">イベントの予約状況は<span class="s1">14/16</span>とまずまずでしたが、</p> <p class="p1">「本当に人が来るのか・・」</p> <p class="p1">「面白くなくて失望されないだろうか・・・」</p> <p class="p1">など色々不安を抱えながら当日を迎えました。</p> <p class="p3"> </p> <p class="p1"><img class="hatena-fotolife" title="f:id:kenjiro_namba:20190528205133p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kenjiro_namba/20190528/20190528205133.png" alt="f:id:kenjiro_namba:20190528205133p:plain" /></p> <p class="p3"> </p> <p class="p1">開始の<span class="s1">11</span>時、参加者は<span class="s1">4</span>人程度・・</p> <p class="p1">「やはりきてくれないかな・・・泣」</p> <p class="p1">と考えていましたが、</p> <p class="p3"> </p> <p class="p1">徐々に参加者が増えていき・・</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:kenjiro_namba:20190528223352j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kenjiro_namba/20190528/20190528223352.jpg" alt="f:id:kenjiro_namba:20190528223352j:plain" /></p> <p class="p3"> </p> <p class="p1"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%E2%A4%AF%A4%E2%A4%AF%B2%F1">もくもく会</a>っぽい!!</p> <p> </p> <p class="p1">初開催のため初見の方が多く、最初は緊張気味でしたが、</p> <p class="p1">お昼ご飯や休憩などを挟み、徐々に交流が!!</p> <p class="p3"> </p> <p class="p1">そして最後は餃子懇親会!!</p> <p class="p3"> </p> <p class="p1"><img class="hatena-fotolife" title="f:id:kenjiro_namba:20190528223429j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kenjiro_namba/20190528/20190528223429.jpg" alt="f:id:kenjiro_namba:20190528223429j:plain" /></p> <p class="p3"> </p> <p class="p1"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%B8%A5%E3%A5%A4%A5%EB">アジャイル</a>に則った自己紹介をしたり、</p> <p class="p1">各自現在の悩みや今後の方向性を話し合ったり、</p> <p class="p1">中には次回イベントで「<span class="s1">LT</span>する宣言!」をしてくださる方もいたり!!</p> <p class="p1">(こういうの本当にありがたいです・・!)</p> <p class="p3"> </p> <p class="p1"><span class="s1">1</span>時間の交流を終え、無事閉会しました。</p> <p> </p> <h3><strong>おわりに</strong></h3> <p class="p1">今回初開催の中、運営側の準備不足なども起こりご迷惑もおかけしましたが、</p> <p class="p1">皆さんの優しさに支えられ、なんとか無事終了することができました。</p> <p class="p2"> </p> <p class="p1">会の中でも何度かお伝えしましたが、</p> <p class="p1"><span class="s1">ProLabo in </span>関西は参加者の皆さんの支えで成り立っています(感謝です。。)</p> <p class="p1">これからも運営側・参加者側関係なく、みんなで盛り上げていきましょう!!</p> <p class="p2"> </p> <p class="p1">ということで、次回参加・<span class="s1">LT</span>発表・その他諸々、ぜひご参加ください!!</p> <p class="p1">(次回はLT行います!!)</p> <p class="p2"> </p> <p class="p1">また、今後の<span class="s1">ProLabo in </span>関西は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%E2%A4%AF%A4%E2%A4%AF%B2%F1">もくもく会</a>に留まらず、様々な形で学びと出会いの場を作っていきます!</p> <p class="p1">その第一弾として、カンボ 🏝沖縄Webエンジニアさん(<a href="https://twitter.com/kanbo0605">https://twitter.com/kanbo0605</a>)主催で以下イベントを行います!</p> <p>こちらもみなさんぜひお越しください!</p> <p class="p2"> </p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Firebase + Vue.js Meetup@大阪 (2019/06/06 19:30〜)" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fre-build.connpass.com%2Fevent%2F132510%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://re-build.connpass.com/event/132510/">re-build.connpass.com</a></cite></p> <p> </p> <p>最後まで、ブログを読んで頂きありがとうございました!</p> <p> </p> kenjiro_namba Nuxt + Contentful + NetlifyでJAM Stackなブログを作ってみよう hatenablog://entry/17680117127117412580 2019-06-05T17:15:53+09:00 2019-07-03T11:13:21+09:00 ITプロパートナーズでエンジニアをしているもりと申します。 少し今更感がありますが、ContentfulとNuxtでJAM Stackなブログを作ってみました! まずJAM Stackという聞き慣れない言葉がでてきましたが、JAM Stackとは Javascript + Api + Markup のことで、HTMLをサーバーサイドで生成してレンダリングするのではなく、javaScriptでapiから取得したデータをもとにデプロイ時にHTMLを生成する最新のweb開発アーキテクチャのことです。 詳しくはこちら 今回はContentfulをAPIとして、Nuxtを静的サイトジェネレーターとして使… <p>ITプロパートナーズでエンジニアをしている<a href="https://twitter.com/frostndays" target="”_blank”">もり</a>と申します。</p> <p>少し今更感がありますが、ContentfulとNuxtでJAM Stackなブログを作ってみました!</p> <p> </p> <p>まずJAM Stackという聞き慣れない言葉がでてきましたが、JAM Stackとは</p> <p><strong><a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a> + <a class="keyword" href="http://d.hatena.ne.jp/keyword/Api">Api</a> + Markup</strong> のことで、HTMLをサーバーサイドで生成して<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>するのではなく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/javaScript">javaScript</a>で<a class="keyword" href="http://d.hatena.ne.jp/keyword/api">api</a>から取得したデータをもとにデプロイ時にHTMLを生成する最新のweb開発<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A1%BC%A5%AD%A5%C6%A5%AF%A5%C1%A5%E3">アーキテクチャ</a>のことです。</p> <p>詳しくは<a href="https://jamstack.org/" target="blank">こちら</a></p> <p> </p> <p>今回はContentfulを<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>として、Nuxtを静的サイトジェネレーターとして使用することで</p> <p>JAM Stackとして実装していきます。</p> <p> </p> <div style="background-color: #f7f8f9; padding: 10px 20px; width: 40%; min-width: 240px; border-radius: 2px;"> <p><strong>Contentful</strong></p> <ol> <li>Contentfulアカウントの作成</li> <li>スペースの作成</li> <li>モデルの作成</li> <li>記事の作成</li> </ol> <p><strong>Nuxt</strong></p> <ol> <li>Nuxtアプリケーション作成</li> <li>Contentfulの設定</li> <li>Contentfulのコンテンツデータ取得</li> <li>ビルド時のhookと動的ルーティングの生成</li> <li>ブログページ</li> </ol> <strong>Netlify</strong> <ol> <li>事前準備</li> <li>アカウント作成</li> </ol> </div> <p> </p> <h4 style="font-size: 150%; padding-bottom: 10px; border-bottom: solid 3px #0083da;"><span style="font-size: 150%;">Contentful</span></h4> <p>Contentfulとはヘッドレス<a class="keyword" href="http://d.hatena.ne.jp/keyword/CMS">CMS</a>と呼ばれるコンテンツ管理サービスで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/WordPress">WordPress</a>のように記事の作成や管理ができます。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/WordPress">WordPress</a>と違うのは、Contentfull側でViewの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>は行わず、完全に<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>ベースである点です。</p> <p>記事のデータは<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を介して取得するため、フロントエンドは自分の好きな技術で好きなように作ることができます。</p> <p> </p> <h4 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">1. Contentfulアカウントの作成</h4> <p>まずはContentfulのアカウントを作成していきましょう。</p> <p><a href="https://www.contentful.com/">https://www.contentful.com/</a></p> <p> </p> <p><span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Try for free </span> とあるのでこちらからアカウントを作成します 。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515112519p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515112519.png" alt="f:id:frostnday:20190515112519p:plain" /></p> <p> </p> <p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Github">Github</a>と連携を行います。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515112906p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515112906.png" alt="f:id:frostnday:20190515112906p:plain" width="341" /></p> <p> </p> <p>名前やメールアドレスなどの必要事項を入力し signUp してアカウントの作成完了です。</p> <h4 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">2. スペースの作成</h4> <p>次に <strong>スペース</strong> という大枠になるものを作成します。</p> <p>スペースとはContentfulの一番大きな領域で、<span style="color: #000000;">1プロジェクト = 1スペース</span>になります。</p> <p><a href="https://www.contentful.com/r/knowledgebase/spaces-and-organizations/" target="”_blank”">公式</a>によると開発用とProduction用といった単位でスペースを作成することもありだそうです。</p> <p>今回はブログを1つ作成するだけなので、スペースは1つでOKです。</p> <p> </p> <p>早速スペースを作成していきましょう。</p> <p>まずは左側にあるメニューから<span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Create space</span>をクリックします。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515124817p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515124817.png" alt="f:id:frostnday:20190515124817p:plain" width="509" /></p> <p> </p> <p>space作成用のモーダルが表示されるので、最初にspaceのタイプを選択します。</p> <p>今回は<span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Freeプラン</span>で作成します。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515125413p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515125413.png" alt="f:id:frostnday:20190515125413p:plain" width="510" /></p> <p> </p> <p>次にスペース名を決めます。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515125837p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515125837.png" alt="f:id:frostnday:20190515125837p:plain" width="513" /></p> <p>今回はシンプルにblogとしておきました。</p> <p> あとは<span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Confirm &amp; CreateSpace</span>をクリックしてスペースの作成完了です!</p> <h4 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">3. モデルの作成</h4> <p>次にモデルを作成していきましょう。</p> <p>モデルとはContentfulで作成する記事などのコンテンツのことです。</p> <p>例えば、 <span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">title</span> <span style="display: inline-block; background-color: #eef4fa; padding: 1px 5px; margin: 0 4px; border-radius: 5px;">top_image</span> <span style="display: inline-block; background-color: #eef4fa; padding: 1px 5px; margin: 0 4px; border-radius: 5px;">create_at</span>のように、一つのコンテンツに持っておきたい情報を名前と型を決めて登録します。</p> <p>今回はブログ記事なので、タイトルやTOP画像、作成日、本文などの情報を持つ記事モデルを作成してみたいと思います。</p> <p> </p> <p>先程スペースを作成したので、TOPページに戻るとCreate spaceが <span style="color: #00cc00;">Completed</span>になっていると思います。</p> <p>そのすぐ下にあるDefine the structure の <span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Create a content type</span> をクリックします。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515144917p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515144917.png" alt="f:id:frostnday:20190515144917p:plain" width="552" /></p> <p>モデルの名前と説明を入力し<span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Create</span>をクリックしましょう。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515145206p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515145206.png" alt="f:id:frostnday:20190515145206p:plain" /></p> <p> </p> <p>今回はブログの記事なのでシンプルにarticleとしました。</p> <p> </p> <p>次に、このモデルにフィールドを設定していきます。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515155447p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515155447.png" alt="f:id:frostnday:20190515155447p:plain" /></p> <p>作成後に右側にある<span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Add Field</span>をクリックします。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515155735p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515155735.png" alt="f:id:frostnday:20190515155735p:plain" /></p> <p>クリックすると追加するフィールドの型を決めるモーダルが表示されるので、Textを選択します。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515155957p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515155957.png" alt="f:id:frostnday:20190515155957p:plain" /></p> <p>フィールドの名前を入力してCreateします。</p> <p>これでText型のブログのタイトルを表すフィールドの作成が完了です。</p> <p> </p> <p>今回は合計5つのフィールドを作成しました。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515160333p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515160333.png" alt="f:id:frostnday:20190515160333p:plain" /></p> <p>これで、1つのコンテンツ(記事)はここで設定した5つのフィールドを持つことができるようになりました!</p> <p> </p> <h4 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">4. 記事の作成</h4> <p> スペース、モデルの作成が終わったので、実際にコンテンツ(記事)を作成してみましょう。</p> <p>TOPページには、先程Modelも作成が終わったので、 Define the structureもCompletedになっているかと思います。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515162113p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515162113.png" alt="f:id:frostnday:20190515162113p:plain" width="561" /></p> <p>  Create your contnet の <span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">Add an entry</span> からコンテンツを作成していきます。</p> <p> </p> <p>このように先程設定したフィールドの中身を入力していきましょう。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515162526p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515162526.png" alt="f:id:frostnday:20190515162526p:plain" width="560" /></p> <p> </p> <p>実際の本文はフィールドタイプをrichTextにしたので、このように文章の途中に自由に画像を差し込んだり、太字にしたり といったことが可能です。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515162940p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515162940.png" alt="f:id:frostnday:20190515162940p:plain" /></p> <p>記事の作成が終わったら、右上の<span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;">publish</span>をクリックして作成完了です。</p> <h4 style="font-size: 150%; padding-bottom: 10px; border-bottom: solid 3px #0083da;"><span style="font-size: 150%;"><br />Nuxt</span></h4> <p>Contentfulでブログ記事の作成が完了したので、次はそのブログを表示するフロントエンドの部分の作成を行います。</p> <p> </p> <p>今回はNuxtを使用してそのフロントエンド部分を作成していこうと思います。</p> <p>NuxtとContentfulの連携については<a href="https://www.contentful.com/developers/docs/javascript/tutorials/integrate-contentful-with-vue-and-nuxt/">公式</a>に手順が載っているのでこちらに沿って説明して行きたいと思います。</p> <h5 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">1. Nuxtアプリケーション作成</h5> <p>では早速Nuxtのアプリケーションを作成していきましょう。</p> <p>まずはvue-<a class="keyword" href="http://d.hatena.ne.jp/keyword/cli">cli</a>を使ってNuxtの新規アプリケーションを作成します。</p> <p> </p> <p>vue-<a class="keyword" href="http://d.hatena.ne.jp/keyword/cli">cli</a>のinstall</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-bash hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;">$ yarn global add @vue/cli-init</code></pre> <p> </p> <p>nuxtアプリケーションの作成</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">$ vue init nuxt/starter アプリケーション名 </pre> <p>これでnuxtのアプリケーションが作成できたので早速Nuxtを起動してみましょう。</p> <p>作成したNuxtアプリケーション配下に移動して必要なmoduleをinstallします。</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">$ yarn install </pre> <p>installが完了したら実際に起動してみます。</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">$ yarn dev </pre> <p>上記コマンドでbuildが開始され、URLが表示されるのでクリックしてみましょう。</p> <p> </p> <p>成功していれば、このような画面が表示されます。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190515170732p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190515/20190515170732.png" alt="f:id:frostnday:20190515170732p:plain" width="467" /></p> <h5 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">2. Contentfulの設定</h5> <p>Nuxtのアプリケーションの作成が完了したので、次はContentfulと連携するのに必要なModuleをinstallします。</p> <p> </p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">$ yarn add contentful </pre> <p> </p> <p>contentfulと連携するには<a class="keyword" href="http://d.hatena.ne.jp/keyword/api">api</a>のキーが必要になるのでそちらを作成しましょう。</p> <p>作成が必要なのは</p> <p>・space id</p> <p>・<a class="keyword" href="http://d.hatena.ne.jp/keyword/access">access</a> token</p> <p>の2つです</p> <p> </p> <p>contentfulのページのヘッダーにあるsettingsからAPIkeysページに移動します。 </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190605133401p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190605/20190605133401.png" alt="f:id:frostnday:20190605133401p:plain" /></p> <p>ページに移動すると、SpaceIDとAccessTokenなどが自動で生成されているのでこちらのキーを使用してcontentfulと連携をしていきます。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190605134328p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190605/20190605134328.png" alt="f:id:frostnday:20190605134328p:plain" /></p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190605134607p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190605/20190605134607.png" alt="f:id:frostnday:20190605134607p:plain" /></p> <p><br />contentfulのキーの作成が終わったらそのキーを保存しておく場所をNuxt側に作ります。</p> <p>プロジェクト直下にenv.jsを作成して以下のようにします。</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"><span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./env.js</span></code><code class="language-json hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"><br />{ <span class="hljs-attr" style="box-sizing: border-box; color: #8091a5;">"CTF_SPACE_ID"</span>: <span class="hljs-string" style="box-sizing: border-box; color: #00cc00;">"先程作成した Space ID"</span>, <span class="hljs-attr" style="box-sizing: border-box; color: #8091a5;">"CTF_CDA_ACCESS_TOKEN"</span>: <span class="hljs-string" style="box-sizing: border-box; color: #00cc00;">"先程作成したContent Delivery API - access token"</span> }</code></pre> <p> ※こちらgitignoreに指定するのをお忘れなく</p> <p> </p> <p> </p> <p>先程installしたcontentful用のmoduleを使うためにpluginを作成します</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"><span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./plugins/contentful.js<br /></span> <span class="hljs-keyword" style="box-sizing: border-box; color: #4a90e2; font-weight: bold;">const</span> <span style="color: #dd830c;">contentful</span> = <span style="color: #2196f3;">require</span>('<span style="color: #00cc00;">contentful</span>')<br /><span class="hljs-keyword" style="box-sizing: border-box; color: #4a90e2; font-weight: bold;">const</span> <span style="color: #dd830c;">env</span> = <span style="color: #2196f3;">require</span>('<span style="color: #00cc00;">../env.js</span>'); <br /><br /><span class="hljs-keyword" style="box-sizing: border-box; color: #4a90e2; font-weight: bold;">const</span> <span style="color: #dd830c;">config</span> = { <br /><span class="hljs-attr" style="box-sizing: border-box; color: #8091a5;"> space</span>: process.env.CTF_SPACE_ID, <br /><span class="hljs-attr" style="box-sizing: border-box; color: #8091a5;"> accessToken</span>:process.env.CTF_CDA_ACCESS_TOKEN <br />} <br /><br /><span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// export `createClient` to use it in pagecomponents</span> <br /><span style="color: #2196f3;">module</span>.<span style="color: #2196f3;">exports</span> = {<br /> createClient () {<br /> <span style="color: #2196f3;">return</span> <span style="color: #dd830c;">contentful</span>.<span style="color: #b388dd;">createClient</span>(<span style="color: #dd830c;">config</span>)<br /> }<br />}<br /></code></pre> <p>これでNuxtとcontentfulの連携の設定は完了です。</p> <h5 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">3. Contentfulのコンテンツデータ取得</h5> <p>連携の設定が完了したので、次はContentfulから先程書いた記事を取得します。</p> <p>今回はJAM Stackにするので、buid時に全ての記事データを取得して<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>に固めます。</p> <p> </p> <p>ではまずデータを取得するmoduleを作成しましょう。</p> <p> </p> <p>modules<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ配下に、先程作成したcontentful連携用のpluginを使用して</p> <p>データ取得する処理を書きます。</p> <p> </p> <p>その後、取得したデータを<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>ファイルとして指定<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに出力します。</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"><span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./modules/createStaticJson.js<br /><br /><span style="color: #2196f3;">import</span> { <span style="color: #dd830c;">createClient</span> } <span style="color: #2196f3;">from</span> '<span style="color: #00cc00;">../plugins/contentful.js</span>';<br /><span style="color: #2196f3;">import</span> <span style="color: #dd830c;">env</span> <span style="color: #2196f3;">from</span> '<span style="color: #00cc00;">../env.js</span>';<br /><span style="color: #2196f3;">import</span> <span style="color: #dd830c;">fs</span> <span style="color: #2196f3;">from</span> '<span style="color: #00cc00;">fs</span>';<br /><br /><span style="color: #2196f3;">const</span> <span style="color: #dd830c;">outputPath</span> = '<span style="color: #00cc00;">static/json/article.json</span>';<br /><br />/**<br /> * Contentfulから取得したデータをjsonファイルとして出力します。<br /> */<br /><span style="color: #2196f3;">export default async function</span> <span style="color: #b388dd;">outputStaticData</span>() {<br /> <span style="color: #2196f3;">const</span> <span style="color: #dd830c;">client</span> = <span style="color: #b388dd;">createClient</span>();<br /><br /> // contentfulからデータを取得<br /> <span style="color: #2196f3;">const</span> <span style="color: #dd830c;">articles</span> = <span style="color: #2196f3;">await</span> <span style="color: #dd830c;">client</span>.<span style="color: #b388dd;">getEntries</span>({<br /> '<span style="color: #00cc00;">content_type</span>': <span style="color: #dd830c;">env.CTF_BLOG_POST_TYPE_ID</span>,<br /> order: '<span style="color: #00cc00;">-sys.createdAt</span>'<br /> });<br /><br /> // jsonとして出力する<br /> <span style="color: #dd830c;">fs</span>.<span style="color: #b388dd;">writeFile</span>(<br /> <span style="color: #dd830c;">outputPath</span>,<br /> <span style="color: #f9ce1d;">JSON</span>.<span style="color: #b388dd;">stringify</span>(<span style="color: #dd830c;">articles</span>),<br /> <span style="color: #dd830c;">err</span> =&gt; {<br /> <span style="color: #b388dd;">if</span> (<span style="color: #dd830c;">err</span>) {<br /> <span style="color: #b388dd;">throw</span> <span style="color: #dd830c;">err</span>;<br /> }<br /> },<br /> );<br />};</span><br /></code></pre> <p> </p> <p>取得した<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>はstatic/<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>/article.<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>に配置するので、指定<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リにはあらかじめ空の<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>ファイルを作成しておきましょう。</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"><span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./static/json/article.json<br /></span>{}</code></pre> <p> </p> <p>これでデータの取得、<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>ファイル出力処理は完了です!</p> <p> </p> <h5 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">4. ビルド時のhookと動的ルーティングの生成</h5> <p>データを取得する処理ができたので、その処理の呼び出し元を作っていきます。</p> <p> </p> <p>今回は nuxt generateという nuxtアプリケーションを<span style="color: #454545; font-family: 'Helvetica Neue', Helvetica, Arial, 'Hiragino Kaku Gothic Pro', Meiryo, 'MS PGothic', sans-serif; font-size: 15.2px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">静的ファイルとして出力するコマンドを使用していきます。</span></p> <p>そのため、このgenerateが実行されたタイミングでデータを取得するようにしましょう。</p> <p> </p> <p> nuxt.config.jsのgenerateの中に以下のように処理を追加します。</p> <p> </p> <p>まず、先程作成したデータを取得して<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>にする処理を呼びます。</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"><span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./nuxt.config.js<br /><br /><span style="color: #2196f3;">import</span> <span style="color: #dd830c;">Articls</span> <span style="color: #2196f3;">from</span> '<span style="color: #00cc00;">./static/json/article.json</span>';<br /><span style="color: #2196f3;">import</span> <span style="color: #dd830c;">outputStaticData</span> <span style="color: #2196f3;">from</span> '<span style="color: #00cc00;">./modules/createStaticJson</span>';<br /><br /><span style="color: #2196f3;">module.exports</span> = {<br /> <span style="color: #2196f3;">generate</span>: {<br /> <span style="color: #2196f3;">async</span> <span style="color: #b388dd;">routes</span>() {<br /> // contentfulからデータを取得してjsonにexportする<br /> <span style="color: #2196f3;">await</span> <span style="color: #b388dd;">outputStaticData</span>();<br /><br /> // 取得したjsonからページを動的生成する<br /> <span style="color: #2196f3;">return</span> <span style="color: #dd830c;">Articls</span>.<span style="color: #dd830c;">items</span>.<span style="color: #2196f3;">map</span>(i =&gt; {<br /> <span style="color: #2196f3;">return</span> `<span style="color: #00cc00;">articles/${i.fields.id}</span>`;<br /> });<br /> }<br /> },<br /><br /> // --- 以下省略 ----</span></code></pre> <p>その後に取得したデータから動的なページを生成します。</p> <p> </p> <p>なぜこのような処理をする必要があるかというと</p> <p>nuxt generateコマンドで静的ページとして出力した場合、</p> <p>article/{id} のような動的URLのページは生成できないからです。</p> <p> </p> <p>今回は</p> <p><span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;"> / </span> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%AD%BB%F6%B0%EC%CD%F7%A5%DA%A1%BC%A5%B8">記事一覧ページ</a></p> <p><span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;"> article/{記事ID} </span> 記事詳細ページ</p> <p> </p> <p>というページ構成で作成するので</p> <p>contentfulから取得した記事IDをもとに記事詳細ページのroutingを追加しています。</p> <p> </p> <h5 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">5. ブログページ</h5> <p> 最後に、実際に表示するページ部分を作成して行きます。</p> <p> </p> <p><span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;"> / </span> <a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%AD%BB%F6%B0%EC%CD%F7%A5%DA%A1%BC%A5%B8">記事一覧ページ</a></p> <p><span style="display: inline-block; background-color: #eef4fa; padding: 1px 8px; margin: 0 4px; border-radius: 5px;"> article/{記事ID} </span> 記事詳細ページ</p> <p> </p> <p>という構成にします</p> <p> </p> <div style="background-color: #f7f8f9; padding: 10px 20px; width: 40%; min-width: 240px; border-radius: 2px;"> <p><span style="color: #777777; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 17.5px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">root /</span></p> <p><span style="font-size: 17.5px;"><span style="color: #777777; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; float: none; display: inline !important;">├ pages/</span></span></p> <p>         <span style="color: #777777; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 17.5px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; float: none; display: inline !important;">├ <span style="color: #00cc00;">index.vue</span></span></p> <p>       <span style="color: #777777; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif;"><span style="color: #777777; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 17.5px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">articles</span></span>/</p> <p>              <span style="color: #777777; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 17.5px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #ffffff; float: none; display: inline !important;">└ <span style="color: #00cc00;">_id.vue</span></span></p> </div> <p> </p> <p>pages 配下の構成がそのままルーティングになるので、こんな感じでvueファイルを配置していきましょう。</p> <p> </p> <p>ここではポイントだけ説明をしていきたいと思います。(vueの説明は省きます)</p> <p> </p> <p>まず、取得した<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>の記事データを表示する方法ですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/json">json</a>をimport してcomputedで参照するのが良いでしょう。</p> <p> </p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"><span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./pages/index.vue<br /><br /></span> <span style="color: #2196f3;">const</span> <span style="color: #dd830c;">Articls</span> = <span style="color: #2196f3;">require</span>('<span style="color: #00cc00;">~/static/json/article.json</span>');<br /><br /> <span style="color: #2196f3;">export default</span> {<br /> <span style="color: #dd830c;">computed</span>: {<br /> <span style="color: #b388dd;">articles</span>(){<br /> <span style="color: #2196f3;">return</span> <span style="color: #dd830c;">Articls</span>.<span style="color: #dd830c;">items</span>;<br /> }<br /> }<br /> }</code></pre> <p> </p> <p>詳細ページに関してはurlから対象の記事のデータを抽出します。</p> <p> </p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"> <span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./pages/article/_id.vue</span><br /><span style="color: #2196f3;">const</span> <span style="color: #dd830c;">Articls</span> = <span style="color: #2196f3;">require</span>('<span style="color: #00cc00;">~/static/json/article.json</span>');<br /><br /><span style="color: #2196f3;">export default</span> {<br /> <span style="color: #dd830c;">computed</span>: {<br /> <span style="color: #b388dd;">article</span>(){<br /> <span style="color: #2196f3;">const</span> <span style="color: #dd830c;">id</span> = <span style="color: #dd830c;">this</span>.<span style="color: #dd830c;">$router</span>.<span style="color: #dd830c;">params</span>.<span style="color: #dd830c;">id</span>;<br /> <span style="color: #2196f3;">return</span> <span style="color: #dd830c;">Articls</span>.<span style="color: #dd830c;">items</span>.<span style="color: #b388dd;">find</span>(<span style="color: #dd830c;">i</span> =&gt; <span style="color: #dd830c;">i</span>.<span style="color: #dd830c;">fields</span>.<span style="color: #dd830c;">id</span> == <span style="color: #dd830c;">id</span>);<br /> }<br /> }<br /><span style="color: #cccccc;">// 以下省略</span></code></pre> <p> </p> <p> </p> <p>今回contentfulでrichテキストを使用して文章の途中に画像をいれたり、太文字にしたり</p> <p>といったことを行っています。この richText を表示する部分は少し処理が必要です。</p> <p> </p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"> <span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./pages/article/_id.vue<br /></span><br /><span style="color: #2196f3;">import</span> { <span style="color: #dd830c;">BLOCKS</span> } <span style="color: #2196f3;">from</span> '<span style="color: #00cc00;">@contentful/rich-text-types</span>';<br /><span style="color: #2196f3;">import</span> { <span style="color: #dd830c;">documentToHtmlString</span> } <span style="color: #2196f3;">from</span> "<span style="color: #00cc00;">@contentful/rich-text-html-renderer</span>";<br /><br /><span style="color: #2196f3;">export default</span> {<br /> <span style="color: #dd830c;">methods</span>: {<br /> <span style="color: #b388dd;">toHtmlString</span>(obj) {<br /> <span style="color: #2196f3;">const</span> <span style="color: #dd830c;">options</span> = {<br /> <span style="color: #dd830c;">renderNode</span>: {<br /> [BLOCKS.EMBEDDED_ASSET]: ({ <span style="color: #dd830c;">data</span>: { <span style="color: #dd830c;">target</span>: { <span style="color: #dd830c;">fields</span> }}}) =&gt;<br /> `<span style="color: #00cc00;">&lt;img src="${fields.file.url}"/&gt;</span>`,<br /> },<br /> }<br /> <span style="color: #2196f3;">return</span> <span style="color: #b388dd;">documentToHtmlString</span>(obj, options);<br /> }<br /> },<br /><span style="color: #cccccc;">// 以下省略</span></code></pre> <p>このように変換するモジュールがライブラリにあるので、こちらを使用して変換の処理を行う必要があります。</p> <p> </p> <p>template内ではv-htmlで指定し、変換メソッドを指定すればOKです。</p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><code class="language-javascript hljs" style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: 0px center; border: 0px; border-radius: 2px; font-weight: 500; overflow: auto; padding: 0px; width: 100%; overflow-wrap: break-word;"> <span class="hljs-comment" style="box-sizing: border-box; color: #a9b9c0;">// ./pages/article/_id.vue</span><br />&lt;template&gt;<br /> &lt;div v-html="<span style="color: #b388dd;">toHtmlString</span>(<span style="color: #dd830c;">article</span>.<span style="color: #dd830c;">fields</span>.<span style="color: #dd830c;">body</span>)"&gt;&lt;/div&gt;<br />&lt;/template&gt;<br /><span style="color: #cccccc;">// 以下省略</span></code></pre> <p> </p> <h4 style="font-size: 150%; padding-bottom: 10px; border-bottom: solid 2px #b9cad5;"><span style="font-size: 150%;">Netlify</span></h4> <p> アプリケーションの実装が完了したので、次は実際にデプロイします。</p> <p>今回はNetlifyという静的なサイトを無料で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A5%B9%A5%C6%A5%A3%A5%F3%A5%B0">ホスティング</a>できるサービスを利用して</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>にpushしたら自動でデプロイされるようにします。 </p> <p> </p> <h5 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">1. 事前準備</h5> <p> 事前準備として、先程作成したアプリケーションをgithuにpushします。</p> <p> </p> <pre style="box-sizing: border-box; font-family: 'Lucida Sans Typewriter', 'Lucida Console', monaco, 'Bitstream Vera Sans Mono', monospace; font-size: 14px; background: #f7f9fa; border: 1px solid #dbe3e7; border-radius: 2px; font-weight: 500; overflow: auto; padding: 1em; width: 730px; overflow-wrap: break-word; display: block; position: relative; color: #536171; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">$ yarn generate</pre> <p> </p> <p>nuxt generateで静的ファイルとしてビルドしましょう。</p> <p>実行後にdistという<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リが作成されていると思います。</p> <p> </p> <p>次に先程作成されたdist<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リをgit.ignoreから外します。</p> <p> </p> <p>最後にgit pushで<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>にpushしたら準備完了です。</p> <p> </p> <h5 style="padding-bottom: 10px; border-bottom: solid 2px #b9cad5;">2. アカウント作成</h5> <p> 次に<a href="https://app.netlify.com/signup">https://app.netlify.com/signup</a>からNetlifyのアカウントを作成しましょう。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190529224337p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190529/20190529224337.png" alt="f:id:frostnday:20190529224337p:plain" /></p> <p>今回は<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>と連携したいので<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>アカウントでsignupします。</p> <p> </p> <p>アカウント作成後のTOPページから<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>と連携していきます。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190529224748p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190529/20190529224748.png" alt="f:id:frostnday:20190529224748p:plain" /></p> <p>New site from Gitをクリックします。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190529225120p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190529/20190529225120.png" alt="f:id:frostnday:20190529225120p:plain" /></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>を選択して認証します。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190529225704p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190529/20190529225704.png" alt="f:id:frostnday:20190529225704p:plain" /></p> <p>Only select repositoriesを選択肢、連携する<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を選択します。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190529230154p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190529/20190529230154.png" alt="f:id:frostnday:20190529230154p:plain" /></p> <p>先程連携した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>を選択肢します。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190529231011p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190529/20190529231011.png" alt="f:id:frostnday:20190529231011p:plain" /></p> <p>連携する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>の設定を行います。</p> <p>① pushがあった場合にhookするブランチを選択します。</p> <p> 今回は masterを選択しておきます。</p> <p> </p> <p>② pushがあった場合に実行するコマンドを入れます。</p> <p>コマンドは何も実行しないので未指定でOKです。</p> <p> </p> <p>③ 実際に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DB%A5%B9%A5%C6%A5%A3%A5%F3%A5%B0">ホスティング</a>する対象となる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リを入力します。</p> <p>NuxtGenerateを行ったときのデフォルト<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リはdistなので今回はdistを指定してましょう。</p> <p> </p> <p>最後に Deploy site でデプロイが開始されます 。</p> <p><img class="hatena-fotolife" title="f:id:frostnday:20190530000029p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/f/frostnday/20190530/20190530000029.png" alt="f:id:frostnday:20190530000029p:plain" /></p> <p>ビルドが成功するとこのようにURLが表示されます。</p> <p> </p> <p>以上でデプロイ設定は完了です。</p> <p> </p> <p>以降はコンテンツをcontentfulで更新したら、ローカルでnuxt generateを実行し</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>にpushすることで自動でデプロイができます。</p> <p> </p> <p>これでJAMStackなブログが完成しました!!!</p> <p> </p> <p>いかがだったでしょうか?</p> <p>比較的お手軽に構築からデプロイまで出来たかと思います。</p> <p> </p> <p>今回のブログは完全な静的サイトなので画像など最適化したりすれば<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B0%A4%C9%F4%B4%B2">阿部寛</a>の速度も夢ではないかもしれません。(笑)</p> <p> </p> <p>サンプルは以下に置いておくので参考にどうぞ</p> <p> </p> <p>サンプルブログ</p> <p><a href="https://reverent-saha-5d7aec.netlify.com/">https://reverent-saha-5d7aec.netlify.com/</a></p> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A5%F3%A5%D7%A5%EB%A5%BD%A1%BC%A5%B9">サンプルソース</a>コード</p> <p> <a href="https://github.com/frostnday/contentful_nuxt_sample">https://github.com/frostnday/contentful_nuxt_sample</a></p> <p> </p> frostnday 【3分でわかる!】CSSスプライトアニメーションをvue.jsのイベントハンドリングで動かしてみよう hatenablog://entry/17680117127076857885 2019-04-25T14:41:07+09:00 2019-04-25T14:41:46+09:00 こんにちは!エンジニアのとみたです。 ささやかなネタですが、CSSで実装できるスプライトアニメーションで遊んでみました。 Twitterのいいねボタンのエフェクトにも使われています。 Twitterのいいねボタン スプライトアニメーション用の画像を用意 今回は、弊社が開催しているもくもく会の非公式キャラクター(笑)「プロラボくん」に動いてもらいます 非公式キャラクタープロラボくん スプライトアニメーション用の画像はこちら。 7つの絵を少しずつ動かします。 スプライトアニメーション用の画像 Illustratorで作成しました。 基準になる絵と枠を一つ作成して、横に並べます。 枠を付けた絵を並べ… <figure class="figure-image figure-image-fotolife mceNonEditable" title="ヘッダ画像"> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425143208p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425143208.png" alt="f:id:tomita_ak:20190425143208p:plain" /></p> </figure> <p>こんにちは!エンジニアのとみたです。</p> <p>ささやかなネタですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>で実装できるスプライトアニメーションで遊んでみました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>のいいねボタンのエフェクトにも使われています。</p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="Twitterのいいねボタン"> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425101438g:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425101438.gif" alt="f:id:tomita_ak:20190425101438g:plain" width="200px" /></p> <figcaption><a class="keyword" href="http://d.hatena.ne.jp/keyword/Twitter">Twitter</a>のいいねボタン</figcaption> </figure> <p> </p> <h3>スプライトアニメーション用の画像を用意</h3> <p>今回は、弊社が開催している<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%E2%A4%AF%A4%E2%A4%AF%B2%F1">もくもく会</a>の非公式キャ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ター(笑)「プロラボくん」に動いてもらいます</p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="非公式キャラクタープロラボくん"> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425101703p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425101703.png" alt="f:id:tomita_ak:20190425101703p:plain" width="100px" /></p> <figcaption>非公式キャ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>タープロラボくん</figcaption> </figure> <p>スプライトアニメーション用の画像はこちら。</p> <p>7つの絵を少しずつ動かします。</p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="スプライトアニメーション用の画像"> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425102718p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425102718.png" alt="f:id:tomita_ak:20190425102718p:plain" /></p> <figcaption>スプライトアニメーション用の画像</figcaption> </figure> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Illustrator">Illustrator</a>で作成しました。</p> <p>基準になる絵と枠を一つ作成して、横に並べます。</p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="枠を付けた絵を並べる"> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425111617p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425111617.png" alt="f:id:tomita_ak:20190425111617p:plain" width="200px" /></p> <figcaption>枠を付けた絵を並べてみました</figcaption> </figure> <p>ここから少しずつ変化をつけていくように作成します。</p> <p>形式は<a class="keyword" href="http://d.hatena.ne.jp/keyword/png">png</a>ではなく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SVG">SVG</a>形式で書き出します。</p> <p>アニメーションが動いたときに、滑らかに描画されます。<a class="keyword" href="http://d.hatena.ne.jp/keyword/png">png</a>だと思った以上にギザギザに...。</p> <p> </p> <h3>HTMLと<a class="keyword" href="http://d.hatena.ne.jp/keyword/CSS">CSS</a>を作っていきます!</h3> <p>vue-<a class="keyword" href="http://d.hatena.ne.jp/keyword/cli">cli</a>を使って新規プロジェクトを作成します。</p> <p>今回はApp.vueをそのまま書き換えてしまいます。</p> <p>まず先程の<a class="keyword" href="http://d.hatena.ne.jp/keyword/SVG">SVG</a>データを表示します。</p> <p> </p> <p>HTMLはこちら。</p> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 14.4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <pre style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; display: block; padding: 0px; margin: 0px; font-size: inherit; line-height: 1.8; word-break: break-all; overflow-wrap: break-word; color: inherit; background-color: transparent; border: none; border-radius: 0px;"><span class="p" style="box-sizing: inherit; color: #e3e3e3;"> &lt;template&gt;<br /> &lt;div id="area"&gt;<br /> &lt;div class="wrap"&gt;<br /> &lt;div class="prorabokunSmile"&gt;&lt;/div&gt;<br /> &lt;/div&gt;<br /> &lt;/div&gt;<br />&lt;/template&gt;<br /></span></pre> </div> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>はこちら。</p> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 14.4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <pre style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; display: block; padding: 0px; margin: 0px; font-size: inherit; line-height: 1.8; word-break: break-all; overflow-wrap: break-word; color: inherit; background-color: transparent; border: none; border-radius: 0px;">.prorabokunSmile {<br /> background: url(./assets/purorabokun_smile.<a class="keyword" href="http://d.hatena.ne.jp/keyword/svg">svg</a>) no-repeat;<br /> background-size: cover; <span style="color: #f5a2a2;">//高さをheightに合わせる</span><br /> width: 100px; <span style="color: #f5a2a2;">//1コマ目の大きさ</span><br /> height: 126px; <span style="color: #f5a2a2;">//1コマ目の大きさ</span><br /> display: inline-block;<br /> margin: 0 50px;<br />}</pre> </div> <p> </p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="最初の1コマ目が表示されました"> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425112234p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425112234.png" alt="f:id:tomita_ak:20190425112234p:plain" width="300px" /></p> <figcaption>最初の1コマ目が表示されました</figcaption> </figure> <h3>いよいよアニメーションを追加していきます!</h3> <p>HTMLはこちら。smileクラスを追加します。</p> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 14.4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <pre style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; display: block; padding: 0px; margin: 0px; font-size: inherit; line-height: 1.8; word-break: break-all; overflow-wrap: break-word; color: inherit; background-color: transparent; border: none; border-radius: 0px;"><span class="p" style="box-sizing: inherit; color: #e3e3e3;"> &lt;template&gt;<br /> &lt;div id="area"&gt;<br /> <br /> &lt;div class="wrap"&gt;<br /> &lt;div class="prorabokunSmile <span style="color: #ff5252;">smile</span>"&gt;&lt;/div&gt;<br /> &lt;/div&gt;<br /><br /> &lt;/div&gt;<br />&lt;/template&gt;<br /></span></pre> </div> <p> </p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/css">css</a>はこちら。</p> <p>smileクラスと、@keyframesを追加しています。</p> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 14.4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <pre style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; display: block; padding: 0px; margin: 0px; font-size: inherit; line-height: 1.8; word-break: break-all; overflow-wrap: break-word; color: inherit; background-color: transparent; border: none; border-radius: 0px;">.prorabokunSmile {<br /> background: url(./assets/purorabokun_smile.<a class="keyword" href="http://d.hatena.ne.jp/keyword/svg">svg</a>) no-repeat;<br /> background-size: cover;<br /> width: 100px;<br /> height: 126px;<br /> display: inline-block;<br /> margin: 0 50px;<br />}<br /><span style="color: #ff5252;">.smile {</span><br /><span style="color: #ff5252;"> animation: animationSmile 1s steps(6) infinite;</span><br /><span style="color: #ff5252;">}</span><br /><span style="color: #ff5252;">@keyframes animationSmile {</span><br /><span style="color: #ff5252;"> to {</span><br /><span style="color: #ff5252;"> background-position: -600px 0;</span><br /><span style="color: #ff5252;"> }</span><br /><span style="color: #ff5252;">}</span></pre> </div> <p> </p> <p>画像を横方向に動かしているだけなのですが、</p> <p>step関数を使い6段階に切り分けて表示させます。</p> <p> </p> <p>step関数の値は、最初に表示される分を差し引いて、コマ数から1引いた値になります。</p> <p>今回は7コマのアニメーションなのでsteps(6)となります。</p> <p> </p> <p>アニメーションには任意の名前をつけ、@keyframesと紐づけます。</p> <p> </p> <p>brackground-positionの値は、widthの値*steps関数の値をマイナスさせた値にすると、うまく動きました。100pxの6倍で-600px、といった具合に。</p> <figure class="figure-image figure-image-fotolife mceNonEditable"><img src="https://i.gyazo.com/f8fad0a39384db1561eb64644f00c18e.gif" alt="Image from Gyazo" width="317" /> <figcaption>動きました!</figcaption> </figure> <div class="highlight">vue.jsのイベントハンドリングを使ってみます。</div> <p>ボタンをクリックすると、 アニメーションが動き出すようにしたい。</p> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 14.4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <pre style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; display: block; padding: 0px; margin: 0px; font-size: inherit; line-height: 1.8; word-break: break-all; overflow-wrap: break-word; color: inherit; background-color: transparent; border: none; border-radius: 0px;"><span class="p" style="box-sizing: inherit; color: #e3e3e3;"> &lt;template&gt;<br /> &lt;div id="area"&gt;<br /> &lt;div class="wrap"&gt;<br /><span style="color: #ff5252;"> &lt;div class="prorabokunSmile"</span><br /><span style="color: #ff5252;"> v-bind:class="{ <span style="color: #f9ce1d;">smile</span>: <span style="color: #dd830c;">isActive</span> }"</span><br /><span style="color: #ff5252;"> &gt;&lt;/div&gt;</span><br /> &lt;/div&gt;<br /><span style="color: #ff5252;"> &lt;button v-on:click="<span style="color: #2196f3;">move</span>"&gt;笑顔!&lt;/button&gt;</span><br /> &lt;/div&gt;<br />&lt;/template&gt;<br /></span></pre> </div> <p> </p> <p>jsの指定はこのようにしました。</p> <div class="highlight" style="box-sizing: inherit; background-color: #364549; color: #e3e3e3; padding: 0.5em; overflow-x: auto; font-family: -apple-system, system-ui, 'Segoe UI', 'Helvetica Neue', 'Hiragino Kaku Gothic ProN', メイリオ, meiryo, sans-serif; font-size: 14.4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> <pre style="box-sizing: inherit; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; display: block; padding: 0px; margin: 0px; font-size: inherit; line-height: 1.8; word-break: break-all; overflow-wrap: break-word; color: inherit; background-color: transparent; border: none; border-radius: 0px;"><span class="p" style="box-sizing: inherit; color: #e3e3e3;">&lt;script&gt;<br /> export default {<br /> name: 'app',<br /> data () {<br /> return {<br /> <span style="color: #dd830c;">isActive</span>: false<br /> }<br /> },<br /> methods: { <br /> </span><span class="p" style="box-sizing: inherit; color: #e3e3e3;"><span style="color: #000000;"><span style="color: #f9ce1d;"><span style="color: #ff5252;"><span style="color: #2196f3;">move</span></span></span></span><span style="color: #e3e3e3;">: function () {<br /> this.</span><span style="color: #dd830c;">isActive</span><span style="color: #e3e3e3;"> = !this.</span><span style="color: #dd830c;">isActive</span><span style="color: #e3e3e3;"><br /> },<br /> }<br /> }<br />&lt;/script&gt;</span></span></pre> </div> <p> </p> <p>「笑顔!」ボタンをクリックするとmoveメソッドが動き、isActiveが切り替わり、アニメーションが定義されたsmileクラスが付与されてキャ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%AF">ラク</a>ターが動いて見える...といった流れです。</p> <figure class="figure-image figure-image-fotolife mceNonEditable"><img src="https://i.gyazo.com/f8dcd126c191fbaaafe3dcba1c7013bc.gif" alt="Image from Gyazo" width="317" /> <figcaption>クリックするとアニメーションが動くようになりました!</figcaption> </figure> <p>お気に入り登録ボタンや画面遷移にも応用していけそうですね!</p> <p> </p> <h3>最後に</h3> <p>ITプロパートナーズでは、Laravel、vue.js、Nuxt.jsで開発に挑戦したいエンジニア・デザイナーを絶賛募集中です!</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Laravel/Vue.js(Nuxt.js)で開発したいエンジニア募集! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F192998" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"></cite></p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="<1人目UI/UXデザイナー求む>デザインに関わる全ての裁量権を任せます! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F83872" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"></cite></p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="今期売上20億を目指すスタートアップ企業でリードエンジニアを募集! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> <p> </p> <p>採用以外でも、様々なイベントを開催予定です!ぜひオフィスに遊びに来てください!</p> <p>今年の3月にオフィスが増床、広くてオシャレなイベントスペースが使えるようになりました!</p> <p>月に数回開催している「プロラボ」「<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%E2%A4%AF%A4%E2%A4%AF%B2%F1">もくもく会</a>」ではたくさんの方に参加いただいています!</p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="新しいイベントスペース"> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425131047p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425131047.png" alt="f:id:tomita_ak:20190425131047p:plain" /></p> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425131051p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425131051.png" alt="f:id:tomita_ak:20190425131051p:plain" /></p> <p><img class="hatena-fotolife" title="f:id:tomita_ak:20190425131054p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/tomita_ak/20190425/20190425131054.png" alt="f:id:tomita_ak:20190425131054p:plain" /></p> <figcaption>新しいイベントスペース</figcaption> </figure> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="ProLabo(プロラボ)" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fitpropartners.connpass.com%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://itpropartners.connpass.com/">itpropartners.connpass.com</a></cite></p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Graspy(グラスピー) | 「未来を掴む」キャリア形成プラットフォーム" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgraspy.jp%2F" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://graspy.jp/">graspy.jp</a></cite></p> <figure class="figure-image figure-image-fotolife mceNonEditable"><img src="https://i.gyazo.com/ac1e74e14b7dcf6adfdecd7a7ed2cdd5.gif" alt="Image from Gyazo" width="317" /> <figcaption>ありがとうございました!</figcaption> </figure> tomita_ak PHPを使い続ける理由 hatenablog://entry/10257846132679952065 2019-03-14T17:16:15+09:00 2019-03-14T17:22:24+09:00 柳澤です。本日のブログを担当します。よろしくお願いします。 ITプロパートナーズは現在5つの事業をやっています。 ITプロパートナーズ事業 上記のサービスは全てPHPを使っています。事業立ち上げ初期の技術選択はとても大切であると考えており、なぜ弊社はPHPを使い続けているのかをお話したいと思います。 [ダイバーシティの環境で開発をしている] 弊社では自社サービスであっても弊社に登録いただいているITプロフェッショナルと一緒に開発する機会が多くあります。現在開発中のGraspy(https://graspy.jp/)では社員以外は在宅で仕事をしてもらう機会も多く、リモートワークで活躍いただいてい… <p>柳澤です。<br />本日のブログを担当します。<br />よろしくお願いします。</p> <p> </p> <p><br />ITプロパートナーズは現在5つの事業をやっています。</p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="ITプロパートナーズ事業"> <p><img class="hatena-fotolife" title="f:id:yuyayanagisawa:20181206122357p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuyayanagisawa/20181206/20181206122357.png" alt="f:id:yuyayanagisawa:20181206122357p:plain" /></p> <figcaption>ITプロパートナーズ事業</figcaption> </figure> <p> </p> <p>上記のサービスは全て<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>を使っています。<br />事業立ち上げ初期の技術選択はとても大切であると考えており、なぜ弊社は<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>を使い続けているのかをお話したいと思います。</p> <h3> </h3> <h3><br />[<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C0%A5%A4%A5%D0%A1%BC%A5%B7%A5%C6%A5%A3">ダイバーシティ</a>の環境で開発をしている]</h3> <p>弊社では自社サービスであっても弊社に登録いただいているITプロフェッショナルと一緒に開発する機会が多くあります。<br />現在開発中のGraspy(<a href="https://graspy.jp/">https://graspy.jp/</a>)では社員以外は在宅で仕事をしてもらう機会も多く、リモートワークで活躍いただいています。<br />弊社の登録者は2018年12月時点で約2万人に登録いただいており、内訳は以下のようになっています。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:yuyayanagisawa:20181204115549p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuyayanagisawa/20181204/20181204115549.png" alt="f:id:yuyayanagisawa:20181204115549p:plain" width="245" /></p> <p><br />9,115人と約半数がエンジニアとしての登録になります。<br />登録者の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>別の割合が以下になります。</p> <p> </p> <p><img class="hatena-fotolife" title="f:id:yuyayanagisawa:20181204115600p:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuyayanagisawa/20181204/20181204115600.png" alt="f:id:yuyayanagisawa:20181204115600p:plain" /></p> <p><br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Javascript">Javascript</a>を得意とするエンジニアが全体の約1/3の割合となっています。</p> <p> </p> <p>弊社の場合、登録いただいているITプロとチームを結成してプロダクト開発に臨んでいます。<br />上記は開発言語に<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>を使い続けている理由のひとつでもあります。</p> <h3> </h3> <h3> </h3> <h3>[初期開発メンバーは自分しかいなかった]</h3> <p><br />僕のエンジニアキャリアは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a>からスタートしました。<br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a> → <a class="keyword" href="http://d.hatena.ne.jp/keyword/C%23">C#</a> → <a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a> → <a class="keyword" href="http://d.hatena.ne.jp/keyword/Objective-C">Objective-C</a> → <a class="keyword" href="http://d.hatena.ne.jp/keyword/Android">Android</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/Java">Java</a> → <a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a> と経験してきたのですが、生産性を考えたら当時<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>で開発するという選択がベストだと判断しました。<br />もちろん<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>以外の言語にも興味はありましたし、他のスタートアップは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で開発しているからうちも<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>でやりたい、という気持ちも正直ありました。<br />ただ会社の立ち上げ当初で人数が少ない中、自分がチームに一番貢献できることはやはり、<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>でサービスをつくるということでした。</p> <p> </p> <p> </p> <figure class="figure-image figure-image-fotolife mceNonEditable" title="立ち上げ当初のオフィススペース"> <p><img class="hatena-fotolife" title="f:id:yuyayanagisawa:20181206122101j:plain" src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yuyayanagisawa/20181206/20181206122101.jpg" alt="f:id:yuyayanagisawa:20181206122101j:plain" /></p> <figcaption>立ち上げ当初のオフィ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%B9%A5%DA">ススペ</a>ース</figcaption> </figure> <p> </p> <p> </p> <h3>[<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>を使っているという劣等感]</h3> <p><br />上でも書きましたが、やはりスタートアップは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で開発している企業が多く、弊社のITプロ紹介サービスでも<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>エンジニアを企業様に紹介する機会がすごく多いです。<br />Goや<a class="keyword" href="http://d.hatena.ne.jp/keyword/Scala">Scala</a>で開発するという技術的挑戦をしている企業もよく耳にしてましたし、うちも新しい言語や技術に挑戦していくべきではないかと悩んだりもしました。<br />エンジニアの集まりに参加すると、未だに<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>を使っている古い企業と思われてしまうのではないかと勝手に思い込み、胸を張って「<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>使ってます」と言えませんでした。</p> <p> </p> <p>この劣等感は自分達が提供するサービスに誇りを持ち、多くの人に価値を提供できるサービスに成長させることによって解消されると考えています。</p> <p>どんな言語を使っても、結局誰にも使われないサービスであれば意味ないですよね。</p> <h3> </h3> <h3><br />[プロダクトの開発言語はどうやって選ぶか?]</h3> <p>以下の3つのポイントが重要だと考えています。</p> <h5><br />スピード感のある開発ができるか</h5> <p><br />スピード感ある開発を行うにはそれまでのノウハウの蓄積が重要であると考えています。<br />今までずっと<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>で開発してきたので、ゼロから新しい開発言語を使用して開発するよりも圧倒的に早くサービスをリリースすることが可能です。</p> <p> </p> <h5>すぐに方向転換できる柔軟性と自由度があるか</h5> <p>サービスの柔軟性、自由度があることにより、試験的にチャレンジできる機会が増えます。<br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>は自由度が高すぎるが故に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%D1%A5%B2%A5%C3%A5%C6%A5%A3%A5%B3%A1%BC%A5%C9">スパゲッティコード</a>になりがちだという問題がありますが、弊社では<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>をちゃんと理解してつかうことにより、ある程度制約を設けています。<br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>構造を理解し、適切な使い方をするために<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DA%A5%A2%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">ペアプログラミング</a>開発を行っています。</p> <p> </p> <h5>人材を採用することができるか</h5> <p><br />サービスがスケールすると開発エンジニアの人数も増やさなければなりません。<br />難易度の高い言語を開発で選択した場合、採用に苦労します。<br />市場にはやはり<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>エンジニアエンジニアが多いという印象があり、弊社の登録状況からもやはり<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>エンジニアが一番採用しやすいことがわかります。<br />人が採用できないが故にプロダクトの成長を止めたくありません。</p> <h3> </h3> <h3><br />[弊社プロダクトの開発例]</h3> <p>2018年10月に新サービスをリリースしました。</p> <p>どんな技術を使っているか紹介します。</p> <p> </p> <p>「未来を掴む」キャリア形成プラットフォーム Graspy</p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Graspy(グラスピー) | 「未来を掴む」キャリア形成プラットフォーム" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgraspy.jp" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://graspy.jp">graspy.jp</a></cite></p> <p> </p> <p>Graspyでは以下を使用しています。 </p> <p> </p> <pre>サーバサイド開発言語:PHP7<br />フロントサイド開発言語:Nuxt.js、Vue.js<br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>:Laravel5.5<br />webサーバ:nginx<br />DB:RDS(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Amazon%20Aurora">Amazon Aurora</a>)<br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%A6%A5%C9">クラウド</a>サーバ:<a class="keyword" href="http://d.hatena.ne.jp/keyword/aws">aws</a>(EC2、S3、ELB、route53、cloudfront、他)<br />その他:docker、SPA、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ADR">ADR</a>、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%E9%A5%E0">スクラム</a>開発、Git Flow</pre> <p> </p> <p>昨年のVueフェスではGoldスポンサーをさせていただきました。</p> <p>先月のLaravelカンファレンスでもスポンサーとして参加させていただきました。</p> <p>今後はフロントでNuxt.jsやVue.jsに積極的に取り入れ、スポンサー活動も続けて行なっていきたいと思っています。 </p> <h3> </h3> <h3>[<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>に感謝している]</h3> <p>現在の弊社のサービスは4年前のスタート当時に比べたら使ってもらえる機会も圧倒的に増え、従業員や弊社に関わってくれるITプロもだいぶ増えました。<br />まだ通過点ではありますが、現在の状況を見ると初期開発を<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>で行うという判断は間違っていなかったと思います。<br />僕自身は色々な経験や出会いをもたらしてくれた<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>という言語に感謝しています。<br /><a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>を使ってプロダクトを成長させ、多くの人に使ってもらえるサービスにしていくことで<a class="keyword" href="http://d.hatena.ne.jp/keyword/PHP">PHP</a>コミュニティに貢献できれば嬉しいです。<br /> </p> <h3><br />[最後に]</h3> <p><br />ITプロパートナーズでは、Laravel、vue.js、Nuxt.jsで開発に挑戦したいエンジニア・デザイナーを絶賛募集中です!</p> <p>少し技術的なことに投資できるフェーズになってきました。<br />今回Nuxt.jsやVue.jsを取り入れたように、チャンスがあれば新しいチャレンジもしていきたいと考えています。</p> <p>ちょっとでも興味を持っていただけたら、まずはお話しましょう。</p> <p>お気軽にご連絡ください!</p> <p> </p> <p> </p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="Laravel/Vue.js(Nuxt.js)で開発したいエンジニア募集! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F192998" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/192998">www.wantedly.com</a></cite></p> <p> </p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="<1人目UI/UXデザイナー求む>デザインに関わる全ての裁量権を任せます! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F83872" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/83872">www.wantedly.com</a></cite></p> <p> </p> <p><iframe class="embed-card embed-webcard" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" title="今期売上20億を目指すスタートアップ企業でリードエンジニアを募集! by 株式会社ITプロパートナーズ" src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.wantedly.com%2Fprojects%2F223387" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://www.wantedly.com/projects/223387">www.wantedly.com</a></cite></p> yuyayanagisawa