Webブラウザレンダリングの仕組み【DOM・DOMツリーって何?】

こんにちは!

株式会社Hajimariでエンジニアインターンをしている溝口と申します!

今回はブラウザレンダリングの仕組みについて書いていきます!

この内容にしようと思ったのは最近、ブラウザ描画周りの知識不足が原因で開発に詰まったことがきっかけです。

これまでずっと理解が曖昧な状態で開発していて、調べることを後回しにしていました。

今回の失敗を機に、曖昧だった部分をしっかり理解しておこうと思い、ブラウザレンダリングについて記事を書くことにしました!

レンダリングの流れ

早速ですがブラウザレンダリングについて、説明をしていきます!

この記事では、レンダリングとはブラウザがデータを受け取ってから、表示するまでの一連の動作のことを指します。 ※加えて今回、DOMツリー構築までのHTML Parseなどについては記事が長くなるため省略します。

以下がレンダリングの全体像です。

  • DOM構築
  • CSSOM tree構築
  • JavaScriptの実行
  • レンダーツリー構築
  • レイアウト
  • ペインティング

自分は初めて全体像を見た時何一つ理解できてませんでした。

1つずつ見ていきましょう。

DOMツリー構築

ブラウザを表示するために最初に行われるのがDOMツリー構築です。

※HTMLはサーバから送られた状態では表示形式に変換されていないため、DOMツリー構築までにいくつかの工程があるのですが、今回はDOMツリー構築までの流れの説明は省きます。

DOMツリーとは

DOMツリーとはこのようなHTMLを表示するためのデータ構造です。

そもそもDOMとは何でしょうか?

Document Object Modelの略で、HTMLやXMLJavascriptなどで外部操作できるインタフェースを定義したものです。

つまりDOMというのはあくまで実体の無いインターフェイス定義のことで、DOMを元にしてDOMツリーというオブジェクトが作られていくんですね。

DOMに基づいて、各ノードがツリー状に作られていきます。

親要素がbody、子要素がdiv4つ、孫がp2つずつの場合、このような形になりますね。

この時に階層が深ければ深いほど読み込みに時間がかかります。

つまり、マークアップをシンプルに書く方が読み込み速度が上がります。

CSSOMツリーの構築

DOMツリーの構築中にCSSファイルやCSSの記述が合った場合、CSSOMツリーというものが構築されます。

DOMツリーと同じツリー状のデータ構造になっています。

CSSOMツリーに書かれた内容が、スタイルとして適用されていきます。

このCSSOMツリーが作られる際にCSSの読み込みを行うのですが、CSSの読み込みのルールが少し複雑になっているので、簡単に説明します。

CSSの読み込みのルールを理解するためには、まず詳細度という概念を理解する必要があります。

※詳細度を重みと表現しているドキュメントもありますが基本的な意味は同じです。

詳細度というのはセレクタの詳細度と言えます。

CSSは、同じ要素に対して複数のセレクタを使っている場合、詳細度の高いセレクタが優先されます。

詳細度が高いとはどういうことか、具体例を見ていきましょう。

こちらをご覧ください。

下のh1要素に対して以下のCSSが書かれています。h1要素の文字は何色になるでしょうか?

<div>
    <h1 class="title"> ...
</div>
div h1 {
    color: green; 
}

.title { 
    color: white; 
}
h1 { 
    color: red; 
}

h1 { 
    color: blue; 
}

正解はdiv h1です。文字はgreenのため緑になります。

h1よりも.titleよりも、div h1の方がセレクタが詳細になっています。そのため、このセレクタが優先されます。

  • クラスセレクタ: クラス名でCSSを指定 例:.title.containerなど
  • 要素セレクタ: 要素名でCSSを指定 例:h1spanなど

クラスセレクタと要素セレクタではクラスセレクタの方が優先度は高いのですが、詳細度のある方が優先されるためdiv h1が優先されます。

この詳細度が詳しいセレクタが優先される概念はカスケードと言われます。

ちなみに、CSSは「Cascading Style Sheets」の略です。(自分はCSSのCがカスケードを意味することを初めて知りました。笑)

カスケードは英語で「滝のように落とす」という意味で、CSSが詳細度の低いものから詳細度の高いものを読み込んでいく特徴を表しています。

詳細度の他にもCSSには継承という概念が読み込みの優先度にも関わっているのですが、今回は割愛します。 継承についてはこちら。 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

JavaScriptの実行

DOMツリーの構築中にJSファイルやJSの記述が合った場合、その場でJSが実行されます。

この時JSの実行が終わるまでDOMツリーの構築はJSの処理が終了するまで中断されます。

つまりCSSと同じようにJSの処理が重ければその分画面表示が遅くなるということですね。

レンダーツリー構築

DOMツリーとCSSOMツリーが作成されると、DOMツリーとCSSOMツリーを1つのツリーにまとめます。

そうして作られるのがレンダーツリーです。

レンダーツリーは画面に表示されるノードのみが作成されます。display: noneの付いてるHTML要素やheadタグなどは含まれないという点に注意です。

レイアウト

レンダーツリーが作成されたら、viewport内に各ノードのサイズや位置を計算して決めていきます。

position: absolute;  width: 33%; などの計算を行います。

ペイント

レイアウトが済んだレンダーツリーをピクセルに変換して、ブラウザ画面に結果を描画します。

まとめ

以上がブラウザ画面の描画の流れになります。

  • ①DOM構築
  • ②CSSOM tree構築(DOM構築に含まれる)
  • JavaScriptの実行(DOM構築に含まれる)
  • ④レンダーツリー構築
  • ⑤レイアウト
  • ⑥ペインティング

普段何気なく使っている裏側の処理を知ることの大切さを今回感じました。サイトのパフォーマンス改善をするためにはブラウザの裏側の処理を理解することは必須です。

それに今回記事を書く中で、HTMLやCSSの書き方が表示速度に直接影響することを知りました。どう動いているのか?どう表示されているのか?と言った仕組みへの理解は良いコードを書くために欠かせない要素だと思います。

今回調べたのはブラウザ描画の流れの中の一部分についてなので、更に詳しく調べて全体像をまとめた記事を後日出そうと思います。

拙い文章にも関わらず、ご精読ありがとうございました!


株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、
一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!

興味のある方は以下の記事をぜひご覧ください!
みなさまとお会いできるのを心からお待ちしております!

www.wantedly.com www.wantedly.com www.wantedly.com www.wantedly.com

『Gather.Town』でリアルオフィスを再現して明日からリモート出社する方法

こんにちは!

実は9月から株式会社Hajimariに入社していた上山です。

インターネットでは 「やむ」 という芸名でやらせていただいております🙏

ITプロパートナーズ事業部にてスクラムマスター兼開発という立ち位置で、チームが幸せに働けるように日々研鑽しています!

メインはZennで書いています😉

zenn.dev

QiitaはQiita Organizationのために使っています😭

qiita.com

twitter.com

Hajimariでは、

現在リモートワーク・オフィス出社をその日の予定に合わせて決めることのできる

365日リモートワークOK制度」を取り入れており、

各メンバーが生産性高く働ける方法を考えて仕事をしております。

(詳しくは↓を見ていただければと思います。)

hajimari.inc

リモートワークを取り入れるにあって大事なのは、

やはりコミュニケーションの取り方なのではないかと思っております。

対面とは違い、気軽にコミュニケーションが取れないようでは

リモートワークの利点も十分に活かしきれなくなってしまいます。

そこで、今回はリモートワークでも社内のコミュニケーションを活発化させる為に

Gather.Townを使ってリアルオフィスを再現してみました!

Gather.Townを使うにあたり

「何か特別な技術が必要なんでしょ?」

と思われる方もいらっしゃると思います。

しかし、今回必要なのは根気です。

何時間も少しずつポチポチする根気さえあれば、誰でも実現可能になるように記事を執筆させていただきました。

ぜひ読まれた方は、ご自分の根気で自社のリアルオフィスを再現し

「社長!Gather.Townでオフィス再現したんで、明日からフルリモートでいいっすか!!!!?」

と提案してみてくださいww👍

■Gather.Townとは

www.gather.town

もう新進気鋭と言われないくらい、定番のオンラインビデオツールになりました。

ご存知でない方にヒトコトで簡単に説明しますと、「昔のドラクエ気分でオンライン通話できる」 ツールになります。

Google Meetでもよくない?」と思われる方もいらっしゃると思いますが、 「実際に、その場にいる」 オフィス感を出すのであればGather.Townのほうが優位です。

朝出社して、自分の席で仕事して、退社時に接続を切る。実際の出社退社のような感覚も味わえるため、一体感通してはオススメです。

「リモートでずっと繋げっぱなしとかヤバ・・・」 と思われたそこの方、Gatherにはプライベートスペース機能があり、「常に繋ぎっぱなし」という状態ではありません。

詳しい説明はここでは省きますが要は 「リモート下でも、一緒に仕事をしている雰囲気」 を作るためのツールです。

■Gather.Townの背景と前景についての前提知識

support.gather.town

まず、前景と背景の概念のざっとした説明をします。

以下の画像のように

  • 背景はキャラクターの下にくるもの
  • 前景はキャラクターと背景の上にくるもの

になります。これを踏まえて前景・背景を分けて作っていく必要があります。

前景と背景

Gather.Townの前景・背景共に設定できる画像は以下の条件です。

  • 3200 x 3200 ピクセル以下
  • 最大ファイルサイズ3MB

「どうやって設定するの?」という点ですが、公式の方でドキュメントを準備してくれているため、参照してみてください。

support.gather.town

ざっくり伝えると、下部メニューバーにあるBuild Toolから

マップ編集へ1

Edit in Mapmakerに移動するとドキュメントと同じマップ編集画面に進めると思います。

マップ編集へ2

■必要なツールとソース

Tiled

www.mapeditor.org

実際に前景・背景画像になるマップを作成していくツールはTiledになります。

基本的にはこのツールを使って制作を進めていきます。

gathertown/mapmaking

github.com

実はGather.Townの公式から既存のマップテンプレートやマップオブジェクトをGithub上で公開されています。

基本的にはこのアセットを使って積み木のように積んでいく作業をしていきます。

Step.0 自社オフィスの見取図を手に入れる

場合によっては一番難しい問題です。会社の見取図やそれに準ずるものを持っていそうな人に聞いて手に入れる必要があります。

もしかしたら、ない場合もあります。(その場合は残念ながら根気度が高くなります)

この工程をちゃんとこなせるかどうかで制作コストが段違いで変わってきます。

ぜひ手に入れられるように全力で努力をしてください🙏

Step.1 MapmakingをCloneする

ここで技術ブログ要素を出しておきます。

git clone git@github.com:gathertown/mapmaking.git

クローンしてきます。めちゃめちゃ容量が大きいので注意してください!!

「Gitの使い方わからないよ><」という方にもZIPでダウンロードする方法を一応載せておきます。

cloneのしかた

Step2. テンプレートをベースに作っていく

mapmaking/maps/templates/Office Modern(Skyscraper)/Office Modern12.tmxを今回はベースに作成していきます。

新しく作ってもいいのですが、アセットのimport関係がちょっと面倒なのでテンプレートを変えるほうが楽で良いです。

テンプレート自体を残しておきたいという方は同じディレクトリ下に複製して名前を変えて編集していきましょう!

基本機能説明

基本的にタイルセットからGather側で用意されているタイルを使用していきます。

凄腕デザイナーとかであれば自分でタイルセットなども自作出来るのですが・・・私はそうではないので、既製品をポチポチ積み木していきます。

ここらへんのツールを主に使います

基本ツール

  • スタンプ(Bキー)
    • 右クリックで範囲選択、その後範囲選択したものをスタンプとして押せます
  • 消しゴム(Eキー)
    • 右クリックで範囲選択消去

ここの部分のツールは基礎です。覚えておきましょう👀

Command + Sで保存です。これを忘れてしまうととてつもなく取り返しのつかない失敗を引き起こす可能性があるので注意してください。

また、レイヤーの概念も重要です。前の章で書いた通り、上にあればあるほど前面に来ます。

レイヤー基本操作

Step3. 不要なものを消していく

レイヤーから装飾となる影などを削除していきます。

そうするとテンプレートからはtopperTile Layer1というものだけになり、topperが前景部分、Tile Layer1が背景部分となります。

もちろん多彩な表現をするためにはここからレイヤーを増やしたりするのですが、基本的な根幹部分はこの2つになります。

そしたらこの2つのレイヤーの中にある不必要なものもすべて消してしまいましょう!

見取図など手に入れる事ができた人はぜひ下地として使ってしまいましょう。

その際に、まずはbaseというレイヤーを最下層に作成し、そこに下地を置いておくことをオススメします。

【タイルセット】の場所に画像をドラッグ&ドロップした後に、こちらの設定で取り込んでしまいましょう。

tileインポート

注意点として、ちょっと画像が小さいかも・・・と思っても拡大する方法が物理的に画像サイズを大きくするしかありません。

他に良いやり方を知っている方がいらっしゃったらぜひ教えていただければなと・・・😭

そして下地として画像を読み込んだ姿は下のようになります。

下地

Step4. 作りたいものを作っていく

ツールの使い方を理解しつつ、少しずつ積み上げていってください・・・。

注意してほしいのは、コーディングと同じでいきなり細部にこだわらないことです。

息切れして挫折する可能性が高くなります。少しずつ、少しずつ積み上げていきましょう。

タイルセットは以下を主に使うと思います。

  • Gather Decoration Exteriorが装飾品
  • floors_3_simple_S20C-5が床タイル
  • toppersが天井素材
  • walltextureが壁素材
  • WindowsObjectsが窓素材
  • Gather Shadows 1.xが影素材

そして、地道に積み上げられたオフィスはこちらになります。

半分完成

基本的に家具類はGatherに取り込んでから設置するので壁と床設置がメインになります。

レイヤー説明

レイヤーの説明ですが

  • object
    • 柱などの前景に含めたいオブジェクトを入れています。
  • wall objects
    • 窓などの壁の上に置きたいものを配置しています。
  • over topper
    • 各部屋の区切りとしての黒い細線の部分で使われています。
  • topper
    • 壁が主にここで配置されています。ここまで前景で主力する想定です。
  • background topper
    • 壁の下部に当たる部分がここで配置しています。
    • ここから背景に含まれる想定です。
    • この分け方をしないと、壁の下にキャラの頭の一部が入ってしまい、微妙に違和感を感じてしまいます。
    • (これ以降の画像ではbackground topperレイヤーは存在していないのですが、テスト段階で気づき手戻りして増やしました)
  • back objects
    • 椅子をここで配置しています。
  • back objects2
    • 椅子が2マス使うケースがあり、隣同士で設置したかったためレイヤーを分けています。
  • carpet
    • カーペットや壇上などの「床の上に設置したいもの」をここに置いています。
  • floor
    • 床です。
  • base
    • 下書き・ガイド線として使った画像のレイヤーです。後で消します。

Extra Step1. 影を付ける

shadowレイヤーを一番上(前面)に作成し影を配置していきましょう。

タイルセットはGather Shadows 1.xです。

影を付けると急に重厚感が出てきました(そんなに変わったように見えませんが・・・)

階段部分は影で上か下に行くかを判別できるようになるので、結構重要な要素かもしれません。

影なしver

影ありver

Extra Step2. 背景を設定する

今のままではなんとも言えない悲しさがあります。そうです。背景がないからです。

しかし、背景まで一から作っていくとしたら制作が何十倍にも膨らんでしまいます。

その上、背景は基本的に見るだけで行けないもの・・・。コストに対するリターンもそれほど大きくありません。

なのでここではGatherで使われている背景を使っていきます。

mapmaking/large splash art/に入っている画像をタイルセットとしてインポートし、使っていきます。

超絶雑ですがこんな感じにまとめました。さっきよりは雰囲気がよくなった(と信じたい)です。

背景あり

Step5. 画像を出力しGatherに設定

前景出力と背景出力の2ステップを行います。

まずは前景から。前景としたいレイヤーだけを表示させほかは非表示にします。

前景だけ

そうしたら、上部メニューバーから

ファイル > 画像でエクスポートを選択し、以下のようなウィンドウで「表示レイヤーのみを含める」をチェックします。

前景画像エクスポート

そしてエクスポートです。これにて前景画像の出力が完了しました。

同様に背景画像の出力も行ってください。

いよいよ取り込みです。

前提知識の章で取り上げた操作方法からマップ編集画面へ移動し、背景画像のインポートと前景画像のインポートを行ってください。

インポート方法

そうすると上手く取り込めます。

Step6. Tile Effectを設定する

今のままだと自由に壁を貫通できたり、行けてほしくない場所に行けてしまいます。

なので、ここからは当たり判定を付けてちゃんと行けないように制限を付けていきます。

Tile Effectのタブから Impassableを選択し、行けてほしくない場所に設定していってください。

障害物設定

そしてObjectsから家具を配置していってください。

家具を配置する

ある程度置き終えるとそれなりに形になってきます。いい雰囲気ですね!!

だいたい完成

Step7. 細かな効果設定をする

会議室で空間を区切れたり、席の近くでしか声が聞こえないようにしたりなど、様々な設定ができます。

エフェクト説明

そしてできた完成品がこちらです!

完成

概ねいい出来ではないでしょうか🤔

前景画像を意識しつつ制作したことにより、柱の後ろにも隠れられるようになりました。

柱の後ろにも入れる

前景画像設定のおかげで、天井の梁も表現されているように見えます。

天井の梁もある

前景背景をきちんと意識しながら作ることがGather.townのマップを作る上で重要なことが分かりますね。

これを意識しないと手戻りが結構発生してしまうので、精神衛生上あまり良くないです😭

■おわりに

Gather.townのマップをカスタムすることで、よりチームの結束力を高めることができます。

コロナ禍でリモート業務が普及した今、昔の「リアルオフィス」を再現することで今一度盛り上がり、関係性の質を高めてもいいのかもしれません。

ハイブリッド勤務を採用している企業では、リアルオフィスの席と座っている人がリンクしているだけで、もし自分がリアル出社した際に「誰が」「どこに」座っているかが分かります。

新卒の方で、なかなかオフィスに来れない方に一体感を感じてもらうためにも良いのでしょうか?

Gather.townでリモートで希薄になってしまった社員間のやりとりが少しでも活発になれば嬉しい気持ちです。

■参考資料

2dgames.jp

dev.classmethod.jp


株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、
一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!

興味のある方は以下の記事をぜひご覧ください!
みなさまとお会いできるのを心からお待ちしております!

www.wantedly.com www.wantedly.com www.wantedly.com www.wantedly.com

【新人エンジニア】MVCモデルの進化版!? ADRが使いやすかったお話

こんにちは!
7月からインターン生として株式会社Hajimariに入社した、難波 慧人です。

現在は、TUKURÜS事業部で受託開発の業務を行っています! 今行っている案件では、サブスクリプション型動画配信サイトの、新規機能開発・運用保守を担当しています!

開発言語に関しては、 バックエンドはPHP(laravelフレームワーク)を用いており、アーキテクチャADR(Action Domain Responder)を採用しています。

案件にジョインした当初、MVCアーキテクチャしか知らない私でしたが、ADRの有用性が少しずつ理解できてきました!

そこで今回は、MVCアーキテクチャと、ADRアーキテクチャの違いについてご紹介したいと思います!!

また、各項目にサンプルコード(ユーザーの一覧、詳細機能)を示していきます!!

MVC(Model View Controller)とは??

引用元( https://white-t007.com/tech/it%E5%9F%BA%E7%A4%8E/mvc/

ユーザーインターフェイスを持つアプリケーションを、モデル(Model)、ビュー(View)、コントローラー(Controller)の3つの責務に分割して、実装していく手法です。

qiita.com

モデル(Model)

モデルとは、アプリケーションの中で、ビジネスロジック、データベースとのアクセスを担っています。例えば、計算、データ取得、データ管理などが挙げられます。

Laravelフレームワークでは、Eloquent ORMと呼ばれる、モデルとデータベースの紐付けを行う機能があります。
Eloquent ORMにより、アプリケーションとデータベース間のデータのやりとりをより円滑にすることができます!! readouble.com

  • サンプルコード
Models/User.php

class User extends Model
{
    /**
     * usersテーブルから全てのユーザーを取得
     * 
     * @return Collection
     */
    public function fetchAllUsers(): Collection
    {
        return User::all();
    }

    /**
     * usersテーブルから指定ユーザーを取得
     * 
     * @return ?User
     */
    public function fetchUser($userId): ?User
    {
        return User::where('id', $userId)->first();
    }
}

ビュー(View)

ビューとは、webサイト上のレイアウトを作成する役割を持っています。主にHTMLで記述されます。また、ビューから生成されたレイアウトはアプリケーションとユーザーのインターフェイスになっています。

ユーザーはこのweb上のレイアウトを通して、アプリケーションにリクエストを送信、アプリケーションからのレスポンスを閲覧することができます。

コントローラー(Controller)

コントローラーとは、ユーザーからのリクエストに応じて、モデルとビューの制御を担う部分です。 実際のユーザーインターフェイスはこのコントローラーになります。アプリケーションとユーザー間のやり取りは全てここを経由します。

ユーザーがwebサイト上で商品一覧をみたい時を例にします。 ユーザーから「商品一覧がみたい」というリクエストが来たら、モデルに商品データを取得するように依頼します。次に取得データをビューに渡し、ビューが作成したレイアウトを、webサイト上に表示しています。これによって、ユーザーがweb上で、商品一覧を閲覧することができます。

  • サンプルコード
Controllers/UserController.php

class UserController extends Controller
{
    /**
     * ユーザー一覧
     *
     * @return \Illuminate\View\View
     */
    public function index(): View
    {
        // Userクラス(モデル)のインスタンス作成
        $userModel = new User();
        // Userクラス(モデル)のメソッドを使って、ユーザーを取得
        $users = $userModel->fetchAllUsers();

        return view('users.index', ['users' => $users]);
    }

    /**
     * ユーザー詳細
     *
     * @param  int  $userId
     * 
     * @return \Illuminate\View\View
     */
    public function show(int $userId): View
    {
        // Userクラス(モデル)のインスタンス作成
        $userModel = new User();
        // Userクラス(モデル)のメソッドを使って、ユーザーを取得
        $user = $userModel->fetchUser($userId);

        // 対象ユーザーが存在しなかったら、404ページを返す
        if(is_null($user)) {
            abort(404); 
        } else {
            return view('users.show', ['user' => $user]);
        }
    }
}

MVCの問題点

  • コントローラーの肥大化
    コントローラーは基本的に、ユーザーインターフェイスとしてアプリケーションの入出力のみを担う責務ですが、コントローラーにビジネスロジックやデーターベースのやり取りを記述される場合があります。

  • モデルの肥大化
    モデルとは、ビジネスロジック・データベースアクセスを書く場所である。しかし、アプリケーションの規模が大きくなると、ビジネスロジック・データベースアクセスも必然的に増えていき、結果モデルは肥大化していきます。。。

ADRの導入

以上の問題点を踏まえ、MVCの改良版でもあるADR(Action Domain Responder)を導入しています!!

ADR(Action Domain Responder)とは??

上記のMVCモデルの改良版として、Paul M. Jonesによって提案されたソフトウェアアーキテクチャパターンです。

引用元:( https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder

ADRでは、アクション(Action)、ドメイン(Domain)、レスポンダー(Responder)の3つの責務に分割して、実装していく手法です!

ADR:wikipedia

アクション(Action)

ユーザーからのHTTPリクエストを受け取り、ドメインの処理結果をレスポンダーに渡す役割です。MVCでいうコントローラー的な位置付けです。 全てのHTTPリクエストはここで処理されます。
HTTPリクエストとは

  • サンプルコード
Actions/User/UserIndexAction.php

class UserIndexAction extends Controller
{
    /**
     * Instance of the Responder property.
     *
     * @var UserRepository
     * @var UserIndexResponder
     */
    protected $userRepository;
    protected $userIndexResponder;

    /**
     * Instantiate the class.
     *
     * @param UserRepository $userRepository
     * @param UserIndexResponder $userIndexResponder
     */
    public function __construct(
        UserRepository $userRepository,
        UserIndexResponder $userIndexResponder
    )
    {
        $this->userRepository = $userRepository;
        $this->responder = $userIndexResponder;
    }

    /**
     * Invoke our action, handle domain, respond.
     *
     *
     * @return View
     */
    public function __invoke(): View
    {
        $users = $this->userRepository->fetchAllUsers();

        return $this->responder->response($users);
    }
}
Actions/User/UserShowAction.php

class UserShowAction extends Controller
{
    /**
     * Instance of the Responder property.
     *
     * @var UserRepository
     * @var UserShowResponder
     */
    protected $userRepository;
    protected $userShowResponder;

    /**
     * Instantiate the class.
     *
     * @param UserRepository $userRepository
     * @param UserShowResponder $userShowResponder
     */
    public function __construct(
        UserRepository $userRepository,
        UserShowResponder $userShowResponder
    )
    {
        $this->userRepository = $userRepository;
        $this->responder = $userShowResponder;
    }

    /**
     * Invoke our action, handle domain, respond.
     * 
     * @param Request $request
     * 
     * @return View
     */
    public function __invoke(Request $request): View
    {
        $user = $this->userRepository->fetchUser($request->user_id);
        
        return $this->responder->response($user);
    }
}

ドメイン(Domain)

アクションから受け取った依頼をもとに、ビジネスロジック、データベースアクセスを担当する役割です。MVCでいうモデル的な位置付けです。
ビジネスロジックとは

今回は、Domainをさらにサービス(Service)、リポジトリ(Repository)、モデル(Model)に分けています。

  • サービス(Service)
    主にアプリケーションビジネスルールを記述する。
    (アプリケーションビジネスルール = システムであるがゆえに発生するビジネスルール)
    例)データに対する複合処理、データ加工など

  • リポジトリ(Repository)
    データベースとのインターフェイスとして使用。
    例)データベースアクセスのみ(加工はサービスの責務)

  • モデル(Model)
    主にエンタープライズビジネスルールを記述する。
    エンタープライズビジネスルール = アプリケーション都合でないビジネスルール)
    例)野球  勝敗:点数が多い方が勝ち 時間:9回で終了 試合人数:各チーム9人ずつ

  • サンプルコード (今回はビジネスロジックがない為、リポジトリのみ)

Domain/User/UserRepository.php

class UserRepository
{
    /**
     * usersテーブルから全てのユーザーを取得
     * 
     * @return Collection
     */
    public function fetchAllUsers(): Collection
    {
        return User::all();
    }

    /**
     * usersテーブルから指定ユーザーを取得
     *
     * @param int  $userId
     *
     * @return ?User
     */
    public function fetchUser($userId): ?User
    {
        return User::where('id', $userId)->first();
    }
}

レスポンダー(Responder)

ドメインの処理結果を受け取り、HTTPレスポンスを作成、処理を担当します。MVC モデルのビューとは異なり、httpステータスの変更、クッキー操作も行います。

  • サンプルコード
Responders/User/UserIndexResponder.php

class UserIndexResponder
{
    protected $view;

    public function __construct(ViewFactory $view)
    {
        $this->view = $view;
    }

    /**
     * 単体動画の表示
     *
     * @param Collection $users
     * 
     * @return View
     */
    public function response(Collection $users): View
    {
        return $this->view->make(
            'users.index',
            compact('users')
        );
    }
}
Responders/User/UserShowResponder.php

class UserShowResponder
{
    protected $view;

    public function __construct(ViewFactory $view)
    {
        $this->view = $view;
    }

    /**
     * 単体動画の表示
     *
     * @param ?User $user
     * 
     * @return View
     */
    public function response(User $user): View
    {
        if (is_null($user)) {
            return \App::abort(404);
        } else
        {
            return $this->view->make(
                'users.index',
                compact('users')
            );
        }
    }
}

MVCからの改善点

  • コントローラーの肥大化
    MVCのコントローラーでは、ユーザーインターフェイスとして、全てのリクエスト、レスポンスを処理していました。
    しかし、ADRではリクエストをアクション、レスポンスをレスポンダーといったように、責務を分けることで改善されています。
    また、ADRでは、1アクションに対し1クラスしか作成しません。これにより、1つのコントローラーにメソッドが集中することを避けれます!!

  • モデルの肥大化
    MVCのモデルでは、全てのビジネスロジックの責務を担当していましたが、サービス(Service)、リポジトリ(Repository)、モデル(Model)という責務に細分化することで、モデルの肥大化を防ぎます。

  • 保守性、可読性の向上
    実際、コードリーディングを行う際に、処理の流れが明確なので追いやすいです。
    また、データーベースアクセスが一目でわかる、追いたい処理を探す時にもファイル特定がしやすい(サービス層の処理ならDomain配下のserviceファイルを探せばいい)などの利点がありました!

■まとめ

以上が、MVCADRの違いでした!!

最初は、「ADRってなんぞや??」と戸惑いましたが、

MVCをある程度理解していれば、ADRのキャッチアップもやりやすいと思います!

是非ともADR(Action Domain Responder)を導入してみてはいかがでしょうか?

Fatなコントローラー 、モデル内で、無限スクロールする開発生活から解放されるはずです。。。


株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、
一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!

興味のある方は以下の記事をぜひご覧ください!
みなさまとお会いできるのを心からお待ちしております!

www.wantedly.com www.wantedly.com www.wantedly.com www.wantedly.com

エンジニア交流会やってみた!!

こんにちは!

2022年5月から株式会社Hajimariでエンジニアをしています、植本です。 現在は、TUKURÜS事業部で主に受託開発の業務を行っています。

今回は先日行った、弊社の23卒内定者を含めた全エンジニアでの「エンジニア交流会」について書いていきます!

というのも、、、

  • 他事業部の社員と話すことがあんまりない

  • (内定者から)オンラインだと距離感が掴めなくて気軽に話せる人は実は少ない・・・

という声を聞いていました…!

リモ―トワークが普及しつつある今、会社によって社員同士の交流の頻度が減っているところもあると思います。

確かにこれじゃ何かあっても殆ど話したことない人に気軽には相談しにくいですよね…。(そりゃそう)

ということで(?)

エンジニア交流会を開催したので、その様子を一挙大公開します!!!

※社員同士の交流は最低限でいい、という方もいると思います。 その考え方も尊重しているので、今回の交流会は「強制」ではなく「自由参加」です!

※でも、都合が合わない方以外はほぼ全員参加してくれました、本当にありがとうございます(歓喜

さてさて前置きが長くなりましたが、、、

  • 株式会社Hajimariのエンジニア組織がどんな雰囲気か知りたい!

  • 弊社エンジニアのリアルな様子が知りたい!

  • 弊社の個や組織へのこだわり/想いが知りたい!

こんなことが気になる方は是非読んでみてください!!

※今回の記事は技術的なことは書いてありませんのでご了承ください、、、

Hajimariの社員ってどんな人がいる?

そもそも弊社やその社員についてどのような印象でしょうか。

自分と仲間の決断と行動、その結果を受け入れて前に進んでいくことのできる人ばかりです!本当に人の良さは声を大にできるのですが、、、

ここで語っても中々イメージ湧かないと思うので最近の記事を紹介しておきますね!(是非覗いてみてください!)

www.wantedly.com

www.wantedly.com

なぜエンジニア交流会を行ったのか

嗚呼、また前置きが長くなってしまったorz

端的に言うと、、、

新しいメンバーも増えてきたので、自己理解と相互理解を深めて自分の目指すべき方向性を再確認してもらうため

にエンジニア交流会を開催することにしました!

弊社は2015年設立のベンチャー企業で、社員数は127名(2022年9月時点)です。新卒採用では23卒を26人採用しておりどんどん大きくなっている会社になります。長野拠点も今年度から本格的に活動をしています!

note.com

そしてエンジニアという括りでいうと、社員数は32人、そのうち23卒内定者9人、インターン生は4人です。

こういった背景を踏まえると、今回の交流会は

  1. 長野拠点/リモートワークで、物理的に普段話せない仲間との交流の場になる点

  2. 全国にいる23卒内定者に、弊社のエンジニア組織や雰囲気を感じてもらえる点

で大きな意義があるものだと思います!

と言っても、会社全体として危機感が募った結果、交流会をしなきゃ!となったわけではありませんでした。

急激に人数が増えたことでコミュニケーションが疎になりつつあることを感じていて、「一回交流の場をあったら良いね」という話から、「じゃ、エンジニア交流会やろっか!」と自然にスタートしました!

自分と仲間の「自立」に向き合える環境を創ることも弊社が大切にしていることなのです。

エンジニア交流会の内容

今回のエンジニア交流会はオンライン開催、3部構成でした!!

  • 一致するまで終われまテン

  • wevox value card

  • Will語り会

1. 一致するまで終われまテン

1つ目の終われまテンは、zoomを使ったアイスブレイクです! 準備の段階では、オンラインだし初対面の人同士もいるしでやや心配していました。。。

でもゲーム後には打ち解けた様子でメインルームへ帰ってきてくれました! ゲームの様子と伴に各ルームで珍回答があったようなので、一部晒しておきます! 知らない観光名所で勉強になりました。

「川」のつく名字で、まず西川が出てくるのか・・・

2. wevox value card

2つ目からは、自分と仲間の価値観や大切にしていることを相互理解をする催しです!

wevox.io

このゲームも今回の趣旨にぴったりのもので、ゲーム中「そんなこと思ってたんだ!」とか「あ〜、どれも捨てがたいいい」などの声が終始続いていましたようです!

終わったあとに少し雑談(いつの間にか人以外が紛れ込んでいました・・・)

3. Will語り会

場も温まってきて、自分の価値観も言語化できたところで、最後の催しです!

できるだけ①事業部が異なる、②年代が異なるを意識してペアを作り、1on1形式でWill語り会をしました。

  1. 自分のWillとそのきっかけ
  2. Hajimariに入社(内定承諾)した理由
  3. 今やっている業務内容や今後やってみたいことについて

過去〜未来の時間軸で自分の価値観や想いを語ってもらいました! アンケートの結果では一番評価が高く、むしろ時間が足りなかったという声が多かったです!

飲みに行くことが決まったメンバーもいたとか笑

(因みに話に夢中になって写真撮るの忘れてしまいました泣)

終わりに ~弊社が大切にしている自己理解と他者理解、組織理解~

終わった後に、アンケートを見ながら運営陣で振り返りをしました!

「終始笑顔が溢れていた空間だった」「内定者が打ち解けた様子で嬉しかった」「交流なので、いろんな他事業の人と話せるコンテンツでも良かったかもしれない」などなど色んな意見をいただきました!!

ただ、アンケート上で良い評価をもらえましたが鵜呑みにするべきではありません。今回参加できなかった人との間に溝が生まれてないか、参加はしていたけど消極的だった人はいなかったか。それに、交流会などのコミュニケーションの場は1回行えば大丈夫というものではありません。かと言って1回1回が長時間となると負担が大きくなるのでそれもまた良くはないように思えます。

反省点や考えることは尽きませんが、、、

こういうたくさんの言葉をいただけただけでも、やって良かったなと素直に感じます!

まだまだ組織には課題が山のようにあります(汗)が、交流会を通して気付けたこと/良くなったこともあります!

それらに対して今後どうするか、続・エンジニア交流会をするのか、、、勉強会をやってみるのか、、、もくもく会をするのか、、、

まぁとにかく色んなことをやってみるので今後の記事に乞うご期待ください!!

こんな感じで(急にまとめに入る)

以上、Hajimariのエンジニア交流会のお話でした〜!


株式会社Hajimariでは、一緒に働く仲間を募集しています!
長野拠点の立ち上げメンバーも大募集しています!

興味のある方は以下の記事をぜひご覧ください!
そして気軽にお話しましょう!! 
みなさまとお会いできるのを心からお待ちしております!

www.wantedly.com www.wantedly.com www.wantedly.com

【Raycast】心地の良いPCライフを送るためのランチャーアプリ

【Raycast】心地の良いPCライフを送るためのランチャーアプリ

こんにちは!
6月から23卒内定者インターン生として株式会社Hajimariに入社した、江端 凌です。

普段は、TUKURÜS事業部で受託開発の業務に携わっています。

今回は普段の業務を効率化するためにインストールしたランチャーアプリであるRaycastが便利すぎたので、この場を借りてご紹介したいと思います。

ランチャーアプリとは

ランチャーアプリとは、ショートカットなどの簡単操作でファイルやフォルダーの呼び出し、起動などが行えるアプリです。

Macをお持ちの方は、⌘ + spaceキーで既存のランチャーアプリである「Spotlight」が使えます。実際に、このSpotlightにアプリケーションの名前入力すると起動してくれます。

support.apple.com

もちろん、検索したいキーワードを入力するとGoogle Chromeで検索結果を表示してくれますし、PCを使う上で痒いところに手が届くアプリなのです。

Raycastをインストールする

では、RaycastがどのようにSpotlightより優れているのかを紹介する前に、実際に使いながら実感してもらうためにインストール方法から説明していきます。

まずはRaycastのサイトに飛んで、「Download for Mac」を押下してください。

www.raycast.com

ダウンロードが開始され、完了するとRaycast.dmgがダウンロードディレクトリ配下に入っています。

クリックして開くと以下のような画面になっていると思うので、

Raycastのインストール画面

左側のRaycastのアイコンをドラッグして、右側のApplicationsディレクトリにドロップします。これでMacのLaunchpadなどからRaycastを探せるようになります。

Raycastを起動したら以下のような表示になります(初回起動は確認事項があるので、全て右下のボタンをクリックでOKです)。

Raycastの初期画面

Raycastのここがすごい

個人的にRaycastがすごいと思う箇所は、その万能さです。

例として、コピーした履歴を表示させる「Clipy」という有名なアプリがありますが、似たようなことがRaycastの「Clipboard History」という機能でも可能です。
もちろん「Clipy」とは使い勝手も違うので、この場でどちらが便利だよというわけではありませんが、よく使われるアプリの機能も包括的にサポートしているという点がRaycastの大きなメリットです。

Raycastの基本機能

ではどのように「Clipboard History」を利用するかご紹介していきます。

まずはoption + spaceキーでRaycastを起動して、検索窓に「Clipboard History」と入力します。

ClipboardHistory

すると、一番上に「Clipboard History」が表示されているので、選択してください。
そうするとコピー履歴が表示されます。

ただ、呼び出すのが面倒ですよね。 Raycastにはわざわざoption + spaceキーで起動して、検索欄で検索をかけなくても良い方法がありますが、それは後述します。

前述した通り、Raycastのすごいところはその万能さです。
Raycastは「Clipboard History」以外に、多くの機能を備えています。

Window Management

WindowManagement

Window Management」は、ウインドウサイズを自在に操作できる機能です。
主に左右に画面分割するときに重宝します。

Snippets

Snippets

Create Snippet」、「Search Snippet」、「Import Snippet」はスニペットを作成、検索できる機能です。
スニペットに登録した内容は、「Keyword」を入力したら呼び出せます。

Quit All Applications

QuitAllApplications

全アプリケーションを落とせます。
PCを使い続けていると、いろんなアプリを開きまくって負荷もかけまくっちゃいがち。
このコマンドで定期的にアプリを落としてあげましょう。

ショートカット

では、そんな便利なコマンドたちをショートカットで呼び出せるようにしていきましょう。

Raycastには「Hotkey」と呼ばれる機能があり、これを使うことでRaycastの機能をショートカットに登録できます。

例として、僕はshift + control + Hに「Clipboard History」登録しています。

登録の方法としては、

  1. option + spaceキーでRaycastを起動。
  2. ⌘ + ,(カンマ)で設定画面を表示。
  3. Extensions から、「Clipboard History」を検索。
  4. Clipboard History」のHotkeyの欄をクリックして、画像のように「Recording...」と表示されたら、登録したいキーを押してください。 ExtensionsHotkey
  5. 一度option + spaceキーでRaycastを閉じて、登録したショートカットで「Clipboard History」が起動したら成功です。

こうすることで、Raycastを開かなくても使いたい機能を使うことができます。
ちなみに、Raycastの他の機能も同様に登録することが可能です。

※ おすすめはshift + control + ⇦ | ⇨に「Window Management」の左半分(Left Half) と右半分(Right Half)。

これでRaycastの体験が爆上がりします。

Raycastの拡張機能

ここまでご紹介した、「Window Management」や「Clipboard History」はRaycast以外でも賄えてしまいます。
全部Raycastで済むというメリットはありますが、同じようなランチャーアプリである「Alfred」でも可能です。

Alfred

Alfred

  • Running with Crayons Ltd
  • 仕事効率化
  • 無料
apps.apple.com

ですが、RaycastとAlfledでは「拡張機能」が大きく違います。
既存のランチャーアプリとしての機能を超えて、便利なアプリが多くリリースされているのですが、Raycastでは基本的に無料で拡張機能が提供されており、対するAlfredは有料です。

では、どんな拡張機能がRaycastにあるのかを少しだけご紹介します。

Brew

www.raycast.com

HomebrewをRaycastで操作できます。
何がインストールされているか確認できたり、アップグレードをすることができます。

Github

www.raycast.com

RaycastでIssueの作成やPRの作成が行えます。
ワークフローの実行まで可能なので、サクッとやりたいときに便利。

Docker

www.raycast.com

Dockerのコンテナを起動・停止できたり、Imageの確認・削除もできます。
自分はDocker for Desktopの挙動がたまにおかしくなるので、重宝しています。

その他Raycastの拡張機能は、Raycast内の「Store」から探すことができるので、気になったものを探してみるもよし、なんなら作っちゃうのもありですね。

Store

zenn.dev


株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、
一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!

興味のある方は以下の記事をぜひご覧ください!
みなさまとお会いできるのを心からお待ちしております!

www.wantedly.com www.wantedly.com www.wantedly.com www.wantedly.com

【PHP】Carbon::now()は誰のnow?

こんにちは、毎日暑いですね。エンジニアの三宅です。

最近、仕事でPHPのコードリーディングを少ししたので、その過程を記載します!

Carbon::now()はOSの現在のシステム時刻

結論はこれです。OSのシステム時刻を持ってきています。

これ以降の内容は、コードを見ながら本当にシステム時刻から取ってきているのかを確認するためのものです。

Carbonとは

今回扱っているCarbonはPHPのDateTimeクラスを継承し、時間操作を簡易化したり便利化したものを提供する拡張クラスになります!

carbon.nesbot.com

これが所謂Carbon::now()の使用例

<?php
require 'vendor/autoload.php';
use Carbon\Carbon;

$now = Carbon::now();
$now->format('Y-m-d H:i:s'); // 2022-08-24 19:31:22

PHPの時間操作

DateTimeはPHP標準クラスです!

www.php.net

Carbonはこれを継承しています。

コードリーディング方針

今回は如何にWEB完結でコードリーディングしていくかの手順を書いていきますが、本格的に処理を追うのであれば debug実行出来る環境を手元に用意すると良いと思います。

WEBでコードリーディング実践

Carbonクラスのコードリーディング

では早速Carbonから追っていきましょう

まずはCarbonのgithubからCarbon::now();の処理を確認します。 github.com

GitHub上で検索する際には"function now"とダブルクォーテーションで括ります こうすると目指す関数に当たりやすいです!

ちなみに対象関数を探すのは、GitHub上よりもgit cloneしてローカルファイルをエディターで検索した方が楽です

ただ、GitHub検索するとissueやcommitメッセージなども対象になるため色んな情報を得る可能性があるというメリットもあります

検索の結果、どうやらここにあるようです!! github.com

nowメソッド

/**
     * Get a Carbon instance for the current date and time.
     *
     * @param DateTimeZone|string|null $tz
     *
     * @return static
     */
    public static function now($tz = null)
    {
        return new static(null, $tz); // ← ここでインスタンス生成して返している
    }

newしたものを返しているようです! new staticは実行されるクラスのインスタンスを生成します。

実行クラスのコンストラクタが第一引数nullで呼ばれますので、そちらの処理を確認

public function __construct($time = null, $tz = null)
    {
        if ($time instanceof DateTimeInterface) {
            $time = $this->constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u');
        }

        if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) {
            $time = static::createFromTimestampUTC($time)->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 = empty($time) || $time === 'now';

        if (method_exists(static::class, 'hasTestNow') &&
            method_exists(static::class, 'getTestNow') &&
            static::hasTestNow() &&
            ($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::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); 
        } catch (Exception $exception) {
            throw new InvalidFormatException($exception->getMessage(), 0, $exception);
        }

        $this->constructedObjectId = spl_object_hash($this);

        if (isset($locale)) {
            setlocale(LC_NUMERIC, $locale);
        }

        self::setLastErrors(parent::getLastErrors());
    }

親のコンストラクタを呼んでいます。

parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null); *

Carbonの親クラスは前述通りDateTimeクラスになります。 そもそも元のコンストラクタの第一引数である$timeはnullできているので、このCarbon→DateTimeのコンストラクタへ移行するタイミングで ’now’という引数が設定されることが読み取れます!

そうすると今度はDateTimeクラスになっていきます

DateTimeクラスのコードリーディング

DateTimeクラスのコンストラクタには説明ドキュメントがありました

www.php.net

PHPの元コードはC言語で書かれています!

GitHubで開発はされていないのですが、ミラーされたコードが上がっています。

github.com

まずは"class DateTime"で検索してみます。

C言語ではなくPHPのstabファイルで引っ掛かりました。

/**
 * Representation of date and time.
 * @link https://php.net/manual/en/class.datetime.php
 */
class DateTime implements DateTimeInterface
{
  ...
}

ここでファイルのディレクトリに注目!! dateを扱ってそうなディレクトリです。

php_date.cが怪しいなといって見ていきます。

今度はブラウザ検索で"PHP_FUNCTION"を見ていきます。

PHP_FUNCTIONC言語のマクロで定義されています。 機能は引数でもらった名前の関数をPHPで動作させるものです。

DateTime classのコンストラクタに当たりそうな処理が見つかりました!

github.com

コメントにReturns new DateTime objectとありますね!

/* {{{ Returns new DateTime object */
PHP_FUNCTION(date_create)
{
    zval           *timezone_object = NULL;
    char           *time_str = NULL;
    size_t          time_str_len = 0;

    ZEND_PARSE_PARAMETERS_START(0, 2)
        Z_PARAM_OPTIONAL
        Z_PARAM_STRING(time_str, time_str_len)
        Z_PARAM_OBJECT_OF_CLASS_OR_NULL(timezone_object, date_ce_timezone)
    ZEND_PARSE_PARAMETERS_END();

    php_date_instantiate(date_ce_date, return_value);
    if (!php_date_initialize(Z_PHPDATE_P(return_value), time_str, time_str_len, NULL, timezone_object, 0)) {
        zval_ptr_dtor(return_value);
        RETURN_FALSE;
    }
}
/* }}} */

上記コードはdate_create()というPHP関数の実行処理内容です!

www.php.net

date_create()を知っていれば、最初からdate_createで検索すればよかったのでは?と思うかと思います。

その通りです!

PHP関数名が分かっていればストレートに検索していけば問題なくたどり着けます!

さて、PHP_FUNCTION(date_create)の中を見てきます。。 php_date_initialize関数が正にその実行処理のようです! コードを追っていきます。

PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, size_t time_str_len, const char *format, zval *timezone_object, int flags) /* {{{ */
{
    timelib_time   *now;
    timelib_tzinfo *tzi = NULL;
    timelib_error_container *err = NULL;
    int type = TIMELIB_ZONETYPE_ID, new_dst = 0;
    char *new_abbr = NULL;
    timelib_sll new_offset = 0;
    time_t sec;
    suseconds_t usec;
    int options = 0;

    if (dateobj->time) {
        timelib_time_dtor(dateobj->time);
    }
    if (format) {
        if (time_str_len == 0) {
            time_str = "";
        }
        dateobj->time = timelib_parse_from_format(format, time_str, time_str_len, &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
    } else {
        if (time_str_len == 0) {
            time_str = "now";
            time_str_len = sizeof("now") - 1;
        }
        dateobj->time = timelib_strtotime(time_str, time_str_len, &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
    }

んー、まだnowの時間を取ってはなさそうですね..!

 /* update last errors and warnings */
    update_errors_warnings(err);

    /* If called from a constructor throw an exception */
    if ((flags & PHP_DATE_INIT_CTOR) && err && err->error_count) {
        /* spit out the first library error message, at least */
        zend_throw_exception_ex(NULL, 0, "Failed to parse time string (%s) at position %d (%c): %s", time_str,
            err->error_messages[0].position, err->error_messages[0].character, err->error_messages[0].message);
    }
    if (err && err->error_count) {
        timelib_time_dtor(dateobj->time);
        dateobj->time = 0;
        return 0;
    }

    if (timezone_object) {
        php_timezone_obj *tzobj;

        tzobj = Z_PHPTIMEZONE_P(timezone_object);
        switch (tzobj->type) {
            case TIMELIB_ZONETYPE_ID:
                tzi = tzobj->tzi.tz;
                break;
            case TIMELIB_ZONETYPE_OFFSET:
                new_offset = tzobj->tzi.utc_offset;
                break;
            case TIMELIB_ZONETYPE_ABBR:
                new_offset = tzobj->tzi.z.utc_offset;
                new_dst    = tzobj->tzi.z.dst;
                new_abbr   = timelib_strdup(tzobj->tzi.z.abbr);
                break;
        }
        type = tzobj->type;
    } else if (dateobj->time->tz_info) {
        tzi = dateobj->time->tz_info;
    } else {
        tzi = get_timezone_info();
        if (!tzi) {
            return 0;
        }
    }

    now = timelib_time_ctor();
    now->zone_type = type;
    switch (type) {
        case TIMELIB_ZONETYPE_ID:
            now->tz_info = tzi;
            break;
        case TIMELIB_ZONETYPE_OFFSET:
            now->z = new_offset;
            break;
        case TIMELIB_ZONETYPE_ABBR:
            now->z = new_offset;
            now->dst = new_dst;
            now->tz_abbr = new_abbr;
            break;
    }

timezone関連の処理が続いてますね。。。

    php_date_get_current_time_with_fraction(&sec, &usec);
    timelib_unixtime2local(now, (timelib_sll) sec);
    php_date_set_time_fraction(now, usec);

きた!

遂に見つけました...!!

php_date_get_current_time_with_fraction

正にというような名前ですね。

static void php_date_get_current_time_with_fraction(time_t *sec, suseconds_t *usec)
{
#if HAVE_GETTIMEOFDAY
    struct timeval tp = {0}; /* For setting microseconds */

    gettimeofday(&tp, NULL);
    *sec = tp.tv_sec;
    *usec = tp.tv_usec;
#else
    *sec = time(NULL);
    *usec = 0;
#endif
}

#if HAVE_GETTIMEOFDAYこの記述はC言語ifdef機能で、条件コンパイラのための機能です。 HAVE_GETTIMEOFDAY0 or 1で定義することで、どちらのコードが取り込まれるかが決まります。

C言語の現在時間取得メソッドである gettimeofday(&tp, NULL);time(NULL); 処理があります。

gettimeofday は POSIX に準拠した関数で、マイクロ秒単位の精度で現在の時刻を取得します。 linuxjm.osdn.jp

timeはC言語の時間ライブラリの関数です。 www.geeksforgeeks.org

ちなみにWindowsのようなOSの場合はどうなるのか??

windows環境ではHAVE_GETTIMEOFDAY1です github.com

windows環境でのgettimeofdayはここにある処理が走ります github.com

結果としてここのコードでwindows OSの時間を取得しています https://github.com/php/php-src/blob/5b01c4863fe9e4bc2702b2bbf66d292d23001a18/win32/time.c#L31

static zend_always_inline MyGetSystemTimeAsFileTime get_time_func(void)
{/*{{{*/
    MyGetSystemTimeAsFileTime timefunc = NULL;
    HMODULE hMod = GetModuleHandle("kernel32.dll");

    if (hMod) {
        /* Max possible resolution <1us, win8/server2012 */
        timefunc = (MyGetSystemTimeAsFileTime)GetProcAddress(hMod, "GetSystemTimePreciseAsFileTime");
    }

    if(!timefunc) {
        /* 100ns blocks since 01-Jan-1641 */
        timefunc = (MyGetSystemTimeAsFileTime) GetSystemTimeAsFileTime;
    }

    return timefunc;
}/*}}}*/

GetSystemTimePreciseAsFileTimeGetSystemTimeAsFileTimeという関数がwindowsの関数ですね。

Windows8以降でより高精度の時間を取得する関数が増えたために分岐があるようです。 metatrading.hatenablog.com

まとめ

ということで、PHPでCarbon::nowをコールするとPHP内部のC言語でOSのシステム時刻を取得しています!

Carbon::now(Carbonクラス) → DateTime(PHP) →gettimeofday/ time (C言語)→ OS ...

OSがどの様に時刻を刻んでいるか、その仕組みなど興味があれば色々調べてみると面白いです。 ja.wikipedia.org

何気なく使っている関数の裏側にはどんな処理が走っているか、WEB上や自分の環境で確認できる手段を持つことは理解を深める一手になるのではないかと思います。

【テックブログ】新卒エンジニアが入社2ヶ月で新規サービスをリリースした話。

こんにちは! 株式会社Hajimari22卒エンジニアの神野 凌太郎です。

普段は、事業部づけのエンジニアとして人事プロパートナーズの開発業務を担当しています。 内定者インターン〜新卒入社後の期間、人事プロパートナーズの開発と並行して 新規サービス「アミーチ」の開発・立ち上げを1人で行いました。

今回は、実際に新規事業の開発・立ち上げを行なって、学んだこと・反省点をまとめてお伝えできればと思います!

■新規サービス「アミーチ」概要

『人事専門型の求人サイト』として立ち上げたサービスです。 もともと、人事プロパートナーズ内で人材紹介を行なっており、 その専用サイトを立ち上げようというところからスタートしています。

人事を扱う求人サイトだからこそ、学校の保健室のように、 気軽にキャリアを考えられる、そんな求人サイトを作りたいという思いから、 「アミーチ」の立ち上げがスタートしました。

現在、リリースから約2ヶ月たち、 ユーザー数、求人数ともに右肩上がりで増えていますので、 正社員人事を採用したい企業様、人事職にチャレンジしたい、キャリアアップをしたいとお考えの求職者様、ぜひご登録をお待ちしております!

biz.ami-chi.com

ami-chi.com

■開発に着手するまで

開発をするにあたって、まずざっくりとした要件定義を行いました。 後述しますが、当時は何よりも早く開発することを優先していたため、 開発の段階では「ざっくりと要件を詰めれば良い」という認識でいました。

サイトに関しては、先に求人を募るために TO B → TO Cサイトの順でリリースを目指すことになりました。

具体的なスケジュールとしては、 TO B向けサイトは5月末、TO C向けサイトは6月中旬のリリースを目指しました。

技術選定は、Laravelとvue.jsを利用し、デザイン部分はbootstrapを利用することにしました。

サービスの立ち上げ・開発自体が初めての経験であったかつ、 1人で開発を行う(LPは外注しました)という状況だったので、 不安を抱えながら開発をすることになりました。

■開発過程〜リリースに至るまで

実際、TO B向けサイトは5月末、TO C向けサイトは6月中旬と スケジュール通りにリリースまで持っていくことができました。 しかし、リリースに至るまでいくつものハードルがありました。

要件が途中でいくつも変更があったこと

最も大変だったハードルは、「途中で要件が変わること」でした。

もともとフワッと始まったプロジェクトだったので、 要件をガチガチに決めていなかったこともあり、ビジネスサイドとの認識齟齬がある状態で 開発を進めてしまったためです。

特に、リリース直前にいくつか改善案や意見をもらったときは、 「本当にリリースできるのか?」と思ったくらい絶望的な状態でした。

ただ、もともとひいていたスケジュールより、 前倒しして開発を進めていたので、優先順位をつけて対応することで リリース時期がズレることなく、リリースすることができました。

学んだこととしては、

  • 時間がかかっても、要件定義はガチガチに固めてから開発をすること
  • キャパを越えそうなときは、優先順位を目に見える形で洗い出してから対応すること

です。

要件定義は、開発スピードを優先していたとしても、 きちんと行なったほうがスムーズに開発できることを身を持って知りました。(当たり前のことかもしれませんが) 最低でも、「テーブル定義」「機能設計」「画面設計」「URI設計」「モデル図」くらいは行なったほうが良いと思います。 アウトプットをあらかじめ行なっておくことで、プロジェクトメンバーとコンセンサスが取れている状態で認識齟齬なく開発を行うことができます。

メインサービス「人事プロ」との開発両立

2つ目は、「サービス開発の両立」です。 弊事業部では、エンジニア2人チームで行っているため、 新規サービスの開発だけに専念できるわけではありません。

別で、先輩エンジニアが大規模の新規サービス開発を行なっていたため、 そちらの開発にかかっきりな状態ということもあり、 メインサービスの「人事プロ」の開発業務も兼任していました。

新規サービスを立ち上げをするにあたって、 大前提メインの事業の収支を成り立たせないといけません。

また事業自体が少数精鋭で運営しているので、 エンジニア観点から常にインパクトのある改善、開発を行なっていく必要があります。

新規プロダクトの開発は楽しいし、やりがいもある一方で、 会社や事業部の誰かがメインの事業にコミットして数字を成り立たせてくれるからこそ、 チャレンジができます。

両立はすごく大変でしたが、改めて数字を成り立たせる重要性を身を持って体感しました。

不安との葛藤

1人で開発していたということもあり、常に不安との葛藤がありました。 特に、技術が成熟しているわけでもなく、「このコード設計で問題なく動くのかな…」といった不安は常につきまとっていました。

加えて、リソースもギリギリな状態で開発を行なっていたので、 納期へのプレッシャーもたくさんありました。

不安を解消するために行なったことは、 開発後の「セルフレビュー」と「テスト」です。

自分が書いたコードが動くわけがないという視点で セルフレビューとテストを行うことによって、サービスのクオリティを維持することができました。

学んだこととしては、開発をひと段落した後に安堵、過信をしないこと。 繰り返し確認・テストすることで、サービスクオリティ維持、不安の解消に繋がることを身を持って知ることができました。

■まとめ

新卒入社でいきなり新規サービスの開発を任される経験は滅多にないと思うので、 かなり良い経験でした!

実際に開発が終わり、リリースした時の感動は凄まじかった…!

まだサービスの改善をするところはたくさんあるので、 より良いサービスを目指して、日々開発に勤しもうと思います!

最後まで読んでいただきありがとうございました!


株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、
一緒に開発を行なっていただけるエンジニア募集しています! 長野拠点の立ち上げメンバーも大募集しています!

興味のある方は以下の記事をぜひご覧ください!
みなさまとお会いできるのを心からお待ちしております!

www.wantedly.com www.wantedly.com www.wantedly.com