Laravelのアップデートを手助けしてくれる「rector-laravel」を使ってみる

いつものtaanatsuです。
今回は、Laravelのアップグレードって大変だなーと思っていたらrector-laravelなるものを発見しました。 github.com

なので少し触ってみようと思います。

rector-laravelとは?

rector-laravelはrectorの拡張で、 rectorとはPHPのコードをアップグレードし、リファクタリングしてくれるもののようです。

nnahito.com

なので、Laravelのアップグレードを補佐してくれるツールですね。

触ってみる

プロジェクトへのインストール

composerでプロジェクトに追加します。
このときに--devをつけて、本番には適応されないようにします。

$ composer require --dev driftingly/rector-laravel

定義ファイルの作成

rector.phpをプロジェクト内に作成します。
中身としては以下です。

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use RectorLaravel\Set\LaravelLevelSetList;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->sets([
        LaravelLevelSetList::UP_TO_LARAVEL_110,        // Laravel11にアップデートするのでこの定数
    ]);
};

リファクタの実行

以下のようなコマンドを実行します。

$ vendor/bin/rector process ディレクトリ名

例えばappディレクトリ配下に適応する場合は以下になります。

$ vendor/bin/rector process app

実行すると解析が行われ、自動的にリファクタしてくれます。

vendor/bin/rector process app
 79/79 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
1 file with changes
===================

1) app/Models/User.php:39

    ---------- begin diff ----------
@@ @@
     /**
      * The attributes that should be cast.
      *
-     * @var array<string, string>
+     * @return array<string, string>
      */
-    protected $casts = [
-        'email_verified_at' => 'datetime',
-    ];
+    protected function casts(): array
+    {
+        return [
+            'email_verified_at' => 'datetime',
+        ];
+    }
 }
    ----------- end diff -----------

Applied rules:
 * AddArgumentDefaultValueRector
 * ModelCastsPropertyToCastsMethodRector
 * StringToClassConstantRector


                                                                                                                        
 [OK] 1 file has been changed by Rector

今回のPHPのコードはLaravelを利用しておりますが、
極力Laravelへの依存度を少なくする方針でコード書いているため、リファクタの範囲は既存のUser.phpだけでした。

SourceTreeで見てみると、リファクタが自動で実行され差分が出ていることもわかります。

このツールがどのくらい業務に耐えうるかはまだまだわかりませんが、少しずつ触っていこうと思いますので、また続報がありましたら更新いたします。
それでは今回はこのへんで。

【Flutter】スクロール挙動をカスタマイズするphysicsについて

こんにちは、エキサイトでアプリエンジニアをしている岡島です。 今回は、ListViewなどスクロールができるウィジェットのphysicsついてまとめていこうと思います。

Scrollableなウィジェット

Flutterには、スクロール可能なウィジェットがいくつか用意されています。主なものには以下があります:

  • ListView
  • PageView
  • GridView
  • SingleChildScrollView
  • CustomScrollView

これらのウィジェットは、physicsプロパティを持っており、このプロパティを通じてスクロールの挙動をカスタマイズすることができます。

physicsについて

physicsプロパティは、スクロールの動作や効果を制御しています。physicsには以下のような種類があるのでそれぞれの特徴について紹介していきます。

  • AlwaysScrollableScrollPhysics
  • BouncingScrollPhysics
  • ClampingScrollPhysics
  • NeverScrollableScrollPhysics
  • RangeMaintainingScrollPhysics

AlwaysScrollableScrollPhysics

AlwaysScrollableScrollPhysicsは、リストがスクロール可能な要素の数にかかわらず、常にスクロール可能にする設定です。、リスト項目が少なくても、スクロール可能な状態が維持されます。

iOSではBouncingScrollPhysicsの挙動、AndroidではClampingScrollPhysicsの挙動となります。

AlwaysScrollableScrollPhysics class - widgets library - Dart API

デフォルト AlwaysScrollableScrollPhysics

BouncingScrollPhysics

BouncingScrollPhysicsは、iOS風のバウンス効果を持つスクロール動作を提供します。リストの先頭や末尾に到達すると、バウンドするようなスクロールです。

BouncingScrollPhysics class - widgets library - Dart API

iOS Android

ClampingScrollPhysics

ClampingScrollPhysicsはAndroidのスクロール動作に近く、リストの先頭や末尾に到達した際に、バウンドせずにスクロールが停止する挙動です。

ClampingScrollPhysics class - widgets library - Dart API

iOS Android

NeverScrollableScrollPhysics

NeverScrollableScrollPhysicsは、スクロールを完全に無効化します。

NeverScrollableScrollPhysics class - widgets library - Dart API

iOS Android

RangeMaintainingScrollPhysics

RangeMaintainingScrollPhysicsは、スクロール範囲が増減しても、自動的にスクロール位置を調整します。 リストの内容が動的に変化する場合に役に立ちます。

RangeMaintainingScrollPhysics class - widgets library - Dart API

最後に

今回はスクロールができるウィジェットのphysicsについてまとめてみました。状況に応じて使い分けることや組み合わせることで、ユーザー体験の向上につながるかと思います。読んでいただきありがとうございました!

Tailwind CSSで連続する要素で交互に適用されるスタイリングをする方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回は、Tailwind CSSで連続する要素で交互に適用されるスタイリングをする方法として、odd:even: Modifierをご紹介します。

交互に適用されるスタイリングの例

冒頭、連続する要素で交互に適用されるスタイリングをしたい場面を整理します。

よくある例の一つに、以下のような行の背景色が交互に変わるテーブルがあります。

行の背景色が交互に変わるテーブルの例

従来のCSSでは、疑似要素である:nth-child()を使用することで実現できました。

引数に奇数番目の場合はodd、偶数番目の場合はevenを指定すると、連続する要素で交互に適用されるスタイルを作成できます。

tr: {
  &:nth-child(even) {
    background: #EEEEEE; // 偶数番目の兄弟要素の背景色が#EEEEEEとなる
  }
}

Tailwind CSSで実現する方法

Tailwind CSS:nth-child(odd)または:nth-child(even)を表現するには、Modifierであるodd:even: を使用します。

先の例をTailwind CSSでスタイリングすると以下のようになります。

<tr class="even:bg-[#EEEEEE]">...</tr>

Tailwind CSSでは、他にも:first-child:nth-last-child()といった疑似要素を表現するためのModifierが用意されています。(詳しくは公式ドキュメントをご覧ください)

さいごに

今回は、Tailwind CSSで連続する要素で交互に適用されるスタイリングをする方法として、odd:even: Modifierをご紹介しました。

Tailwind CSSを使用している方の一助となれば幸いです。

ご精読ありがとうございました。

【Flutter】RadioListTileのラジオボタンとテキストの間隔を調整する

こんにちはエキサイトでアプリエンジニアをしている岡島です。今回はRadioListTileを用いた時、ラジオボタンとテキストの間隔を調整したい時にどうすればいいかについて共有しようと思います。日本語での記事が無かったので、皆様のお役に立てれば幸いです。

RadioListTileについて

RadioListTileは、ラジオボタンの横にテキストを表示させリスト形式で表示することができる便利なウィジェットです。

RadioListTile class - material library - Dart API

以下のサンプルコードのようにリストで保持した情報を表示することができます。 サンプルコードの詳細はこちらの記事を参考にしてください。

FlutterでHookWidgetを使用してRadioListTileを作成しよう! - エキサイト TechBlog.

class MyHomePage extends HookWidget {
  const MyHomePage({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    final sampleList = [
      'サンプル1',
      'サンプル2',
      'サンプル3',
      'サンプル4',
      'サンプル5',
    ];

    final selectedIndex = useState(0);

    return Scaffold(
      appBar: AppBar(
        title: const Text('ラジオボタン'),
      ),
      body: ListView.builder(
        itemCount: sampleList.length,
        itemBuilder: (_, index) {
          final sampleItem = sampleList[index];
          return RadioListTile(
            title: Text(sampleItem),
            value: index,
            groupValue: selectedIndex.value,
            onChanged: (int? value) {
              selectedIndex.value = index;
            },
          );
        },
      ),
    );
  }
}

ラジオボタンとテキストの隙間を調整する

以下のサンプルコードのようにThemeウィジェットを利用して、 ListTileThemehorizontalTitleGapプロパティを使用することで隙間を調整できます。 RadioListTileはListTileThemeを使用しているためListTileThemeを変更するとRadioListTileにも反映されます。

ListTileTheme, which can be used to affect the style of list tiles, including radio list tiles. (RadioListTile class - material library - Dart API参照)

return Theme(
  data: Theme.of(context).copyWith(
    listTileTheme: const ListTileThemeData(
      horizontalTitleGap: 0,
    ),
  ),
  child: RadioListTile(
    title: Text(sampleItem),
    value: index,
    groupValue: selectedIndex.value,
    onChanged: (int? value) {
      selectedIndex.value = index;
    },
  ),
);

添付写真は左側が通常のRadioListTile、右側はhorizontalTitleGapを0に設定したRadioListTileです

左:調整前 右:調整後

さいごに

今回はRadioListTileウィジェットを用いた時の、ラジオボタンとテキストの隙間を調整する方法をご紹介しました。日本語の記事でこのような方法を紹介しているものがなかったので記事にしてみました。この記事がお役に立てれば幸いです。

紙のデザインは必ずラスタライズ!

こんにちは!エキサイトお悩み相談室でデザイナーをしているサヅカです。

9月になっても暑い日々ですが溶けてなんていられません…!今月も張り切ってデザインしていきます!

レイヤー上では透けていないのに背景が透過している!?

以前展示会用にパネルを作った時の話ですが、実は印刷トラブルがありましたので紹介させていただきます。

A1サイズのパネル印刷が届いたので早速チェックしていると…透けている!イラレデータは透過させていないのになぜ…?

背景の黄色い椅子が透けている状態

さらにドットのパターンが網目状に

また、パラペット(ブースの上部看板)に使用したイラストの服のドットにパターンの線が残っていました。 集合体恐怖症としては心臓バクバクです(`;ω;´)

近くで見るとはっきり見えてしまうパターンの線

ラスタライズが必要だった

順調に思われた展示会準備、ここにきて暗雲が…

すぐに印刷会社に電話して、確認後に折り返しが来ました。

「ラスタライズされていないようなのですが、入稿前にラスタライズ処理されましたか?」

「え?」

「え?」

「すいません、してないです…」

「印刷事故を防ぐために、必ずラスタライズという処理をしていただきたいのです」

「!!」

…なんということだ……

知らなかったので丁寧に教えていただきました。

ラスタライズとは、ベクトル画像をピクセル画像に転換し、画像データを軽量化することです。 効果や機能は印刷時にうまく読み込むことができず、色が変わってしまったり変な線が入ってしまったりと思いもよらない印刷の仕上がりにつながります。

↑まさにこれですね。

反省

デザイナー歴10数年、印刷物のデザイン〜入稿まで何度かやってきていますが、お恥ずかしながらラスタライズして入稿は一度も行ったことがありませんでした。

今まで運良く変な仕上がりにならなかったか、あるいは印刷会社さんが気を遣って対応してくださっていたんだなと思います(申し訳なさ極まりない)

今後は必ずラスタライズするようにして印刷事故を防ぎたいと思います(`;ω;´)

Tailwind CSSでタグのベーススタイルを設定する方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回は、Tailwind CSSでタグのベーススタイルを設定する方法をご紹介します。

実現したいこと

冒頭、実現したいことを整理します。

例えば、サイト内のh1タグのスタイルをすべて共通にしたい場合、カスタムクラスを用意するという手があります。

@layer components {
  .heading-1 {
    @apply font-body text-2xl
  }
}
<h1 class="heading-1">文字には「font-body text-2xl」が適用される</h1>

しかし、このアプローチの方法ですと、毎回クラスを呼び出す必要があったり、そもそもこのクラスの存在自体を周知する必要が生じます。

そこで、従来のCSSのようにクラスを使用せずにタグに対して直接スタイルを記述して、クラス指定なしスタイリングできるようにしたいです。

h1 {
  @apply font-body text-2xl
}
<h1>文字には「font-body text-2xl」が適用される</h1>

@layer baseでベーススタイルを設定できる

タグのベーススタイルを設定したい場合、Tailwind CSSを読み込んでいるCSSファイルに@layer baseカスケードレイヤーを追加すると設定ができます。

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  h1 {
    @apply font-body text-2xl
  }
}

これでクラス指定なしスタイリングが可能になりました。

さいごに

今回は、Tailwind CSSでタグのベーススタイルを設定する方法をご紹介しました。

Tailwind CSSを使っている方の一助となれば幸いです。

ご精読ありがとうございました。

Alpine.jsでページ読み込み時に非表示要素が一瞬見えてしまう「blip」を防止する方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回はAlpine.jsを使用している環境下で、ページ読み込み時に隠している要素が一瞬表示されてしまう「blip」という現象を防止する方法をご紹介します。

blipとは

冒頭、Alpine.jsにおけるblipとはなにかを整理します。

例えば以下の場合、pタグにはx-showディレクティブによって、isOpentrueになると表示されるという条件が設けられています。

したがって、ページ読み込み時には表示されないことが期待されます。

<div x-data="{ isOpen: false }">
  <button @click="isOpen = true">クリック</button>
  <p x-show="isOpen">ボタンをクリックしたら表示される文字</p>
</div>

しかし、ページが読み込まれた後、Alpine.jsの読み込みが完了する前の一瞬、初期化されていないテンプレートが表示されてしまうことがあります。

これを「blip」と呼びます。

どうやってblipを防止するか

blipは、ページ読み込み時には隠れているべき要素にx-cloakディレクティブを付与することで防止できます。

先の例ですと、ページ読み込み時には隠れているべき要素はpタグですので、以下のようにx-cloakを付与します。

<p x-cloak x-show="isOpen">ボタンをクリックしたら表示される文字</p>

ただし、これだけでは機能しません。必ず、CSSに以下のスタイルの追加が必要です。

[x-cloak] { display: none !important; }

これによって、x-cloakが付与された要素はAlpine.jsの読み込みが完了するまでdisplay: none;が適用され、blipの発生を防ぎます。

さいごに

今回はAlpine.jsを使用している環境下で、ページ読み込み時に隠している要素が一瞬表示されてしまう「blip」という現象を防止する方法をご紹介しました。

Alpine.jsを使用されている方の一助となれば幸いです。

ご精読ありがとうございました。

【Flutter/Dart】sealedクラスを用いて、SNSフィード画面のような複数の型を持つリストの表現する

こんにちは、エキサイト株式会社でアプリエンジニアをしている岡島です。今回はsealedクラスを用いて複数の型を持つリストを表現する方法について共有したいと思います。

複数の型を持つリストの表現

例えば、SNSのフィード画面のように投稿、広告、おすすめユーザーなどをまとめてリストで持ちたいときはどうすればいいか、sealedクラスを用いた実装を見ていきたいと思います。

実装例

sealedクラスを用いた型の定義

以下の例では、フィード画面に存在する投稿(Post)、広告(Ad)、おすすめユーザー(UserSuggestion)という3つの異なる型を定義しました。

sealed class FeedItem {}

class Post extends FeedItem {
  Post(this.content);
  final String content;
}

class Ad extends FeedItem {
  Ad(this.title);
  final String title;
}

class UserSuggestion extends FeedItem {
  UserSuggestion(this.username);
  final String username;
}

投稿、広告、おすすめユーザー用のウィジェットの用意

今回の例では、SNSのフィード画面を想定し、投稿・広告・おすすめユーザーという三種類のウィジェットを用意します。

例では簡単に、Textウィジェットの色をそれぞれ変えるようにしました。

class PostWidget extends StatelessWidget {
  const PostWidget({super.key, required this.post});
  final Post post;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        '投稿: ${post.content}',
        style: const TextStyle(color: Colors.black),
      ),
    );
  }
}

class AdWidget extends StatelessWidget {
  const AdWidget({super.key, required this.ad});
  final Ad ad;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        '広告: ${ad.title}',
        style: const TextStyle(color: Colors.red),
      ),
    );
  }
}

class UserSuggestionWidget extends StatelessWidget {
  const UserSuggestionWidget({super.key, required this.suggestion});
  final UserSuggestion suggestion;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(
        'おすすめユーザー: ${suggestion.username}',
        style: const TextStyle(color: Colors.blue),
      ),
    );
  }
}

sealedクラスのパターンマッチを利用したリストの表示

続いて、sealedクラスのパターンマッチを利用して、リストを表示していきます。 この実装により、異なる型のアイテムを一つのリストで管理し、それぞれのアイテムでウィジェットを使い分けることができます。

class MyHomePage extends StatelessWidget {
  MyHomePage({super.key, required this.title});
  final String title;

  final List<FeedItem> feedList = [
    Post('Hello world!'),
    UserSuggestion('user1'),
    Ad('Ad1!'),
    Ad('Ad2!'),
    Post('hoge!hoge!'),
    UserSuggestion('user2'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: ListView.builder(
        itemCount: feedList.length,
        itemBuilder: (context, index) {
          final item = feedList[index];
          return switch (item) {
            final Post post => PostWidget(post: post),
            final Ad ad => AdWidget(ad: ad),
            final UserSuggestion suggestion =>
            UserSuggestionWidget(suggestion: suggestion),
          };
        },
      ),
    );
  }
}

実際の画面

sealedクラスの利点

  • バグになりにくい
    sealedクラスを使用することで、FeedItemの全てのサブタイプを網羅的に処理することできます。 新しい種類のフィードアイテムを追加した場合、コンパイラがパターンマッチングの不足を指摘してくれるため、バグの早期発見につながります。

  • コードの可読性向上
    異なる型のリストを一箇所のリストで保持することができるため、プログラムを簡潔結に書くことができます。

  • サブクラスごとに異なるパラメータを持つことができる
    Postのクラスにいいね数やimageを持たせ、UserSuggestionにはユーザーネームやフォロー状態を持たせるというように、各サブクラスに合わせたデータ構造を定義することができます。

まとめ

sealedクラスを使用することで、SNSのフィード画面のような複数の型を持つリストを扱う実装ができました。他にもAPIのレスポンスを扱う時なども役に立ちそうです。sealedクラスについて今まで触ったことがなかったのですが、実際に使ってみると結構便利だということが分かりました。

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

Excite就業型インターンでのフレームワーク移行タスク

2024年8月5日からおよそ一ヶ月間に渡ってエキサイト株式会社のLife&Wellness事業部で就業型インターンをさせていただいたかわいです。インターンの参加理由からこのインターンで得られたことを書いていきます。

誰向け?

インターンを探している、または参加しようか迷っている人向けです。私はインターン参加にあたりいくつか狙いを持って参加し、その狙いがどうだったかについて学びで書いています。何ができたかについても触れているので、私と同じような課題感を持っている人がインターンに参加するきっかけになれば、と思っています。

自己紹介

普段は大学院でセキュリティ関連の研究を行っています。研究とは別に個人の趣味でTypeScriptやGoを使ったWebアプリの開発を2年ほど行っています。これまで技術系のインターンやアルバイトに参加したことがなく、開発経験が足りていないと感じていました。経験不足を埋めるために、個人ではできないチーム開発の経験を積むこと、実運用されているコードがどのようなものかを知ること、が可能であるインターンに参加することを考えました。

そのような中でエキサイト株式会社のLife&Wellness事業部にインターン生として関わらせていただきました。リポジトリパターンやユースケースなど、これまで気にしてこなかった変更の容易さや保守性の点で大いに助けていただきました。

インターンでやったこと

Life&Wellness事業部は主に占いサービスの運営を行っています。その中で私が関わったタスクは「excite電話占い」のPHPフレームワークをBEARからLaravelへと移行するタスクです。同じタイミングでインターンに参加していた方とメンティー二人、メンター二人体制で進めていきました。

占い特集ページの移植が担当範囲でしたが、私にPHPの経験がなかったことから、ロジックを含まないページの表示のみを行うことから割り振っていただきました。その後、データの処理のロジックを含むページの移植に取り組みました。2つ目のタスクで移植元と処理のフローが変わったのでサンプルとして一部のクラスを用意してくださりました。開発をしやすくするためのCI/CDツールも豊富で、レビューも丁寧に行ってくださいました。何より、何故変更するのかの理由を明確に説明してくださりとても参考になりました。

学び

2種類の経験を積むことを目的にインターンに参加しました。それぞれどんな結果が得られたのかまとめました。

チーム開発

個人開発と最も異なる点として「明確にタスクが割り振られ期限が存在する」という点がありました。そのため「どこに時間を掛ければいいのか」に気を使いました。そのため、どこに時間を掛ければいいのか、という点には気を使いました。とはいえ、普段あまり意識していなかった時間の割り振りがすぐにできるとは考えていなかったので、自分で抱え込みすぎないことを意識していました。気をつけていたのは自分ができていない、または理解していないことへの対処方法です。自分が詰まっていることが調べればわかることなのか、それともプロジェクト固有で聞かなければわからないことなのかの判別をつけることが出来ないため、時間制限を設けていました。決まった時間調べ、それでも成果が得られなかったときにメンターに質問していました。どの程度時間をかけていいのかは初め自分で決めてしまいましたが、途中でメンターに聞いた方が良い事に気づきました。気づいた後で、ミーティングの際にどれだけ時間をかけて良いのか確認することができたので共通認識を作ることができました。自分の学びと進捗のバランスについてはある程度うまくいっていたと思います。

逆にもっと別のやり方があったかなと思ったことはメンターへの質問の仕方です。わからないところに対して何がわからないのかを把握して、調べたことや取り組んだことを説明することは意識していましたが、あまりうまくいきませんでした。ロジックを含むページの移植でデータ取得があったのですが、初めてユースケースに触れたので、全てがわからないことでした。そのため、調べた事と理解したことの区別がうまくできませんでした。実装のサンプルと睨めっこしながら、まだ整理できていないことをメンターに聞き続けることになってしまいました。わからないことがひとつ解消されたら、焦らず自分でまた次のエラーに取り組めば良かったのですが、大量のわからない事に押し流されてしまいました。今回の経験でわからない事を一度に全て解消しようとすることはかなり無謀だと分かりました。わからないことを整理するために問題の構造化をできるようになる、という課題が見つかりました。これは個人開発でもできることなので、意識して取り組んでいくつもりです。

コーディング

今回のインターンでの一番の学びは変更容易性やテストしやすい書き方です。

あるページの実装において、コンフィグからデータを読み込んでいるところがありました。このプロジェクトでは、レイヤードアーキテクチャのような構成になっています。そのため、ロジックの分離がリポジトリーやユースケースを用いることで行われています。私が取り組んだタスクでは次のような処理の流れになっていました。

  1. コンフィグからリポジトリーがデータの読み込みを行う
  2. ユースケースリポジトリーが取ってきたデータを整形し、コントローラーに渡す
  3. コントローラーはビューモデルを作り、bladeファイル(Laravelのテンプレートファイル)に引き渡しページビューになる

リポジトリーの実装として初めに私が書いたのは次のようなコードになります。

<?php

final readonly class ResultRepository implements RepositoryInterface
{
    public function __construct(ResultKey $resultKey)
    {
        $resultList = $this->getResultList()
        return $resultList[$resultKey]
    }

    public function get(): Result
    {
        return $this->result;
    }

    /**
     * @return array<string, Result> string is resultKey
     */
    private function resultList(): array
    {
        $result = include './resultList.php';
        return $result;
    }
}

問題点

  • includeで直接読み込みを行なっているのでテストしにくい

相対パスで指定先から読み込みをして、指定されたキーの有無で結果が変わります。テストを書いた方が望ましいコードですが、テストのたびに毎回ファイルアクセスをしなければなりません。並列実行できなくなってしまう上にコンフィグに正しく結果が入っている保証さえありません。

  • get()で直接result | nullを返している

たまたま返却値が1種類のデータのみで済んでいますが、複数のデータを返却しなければならなくなった時に大きな変更が必要になります。

解決策

  • ファイル読み込み

Laravelのヘルパー関数でファイル読み込みを行いました。この変更によって、相対パスを使わなくてよくなった上に、テストではモックすることでファイルアクセスをなくせました。

  • get()の返り値

値オブジェクトResultOutputを作り、resultOutputからのデータ取り出しはユースケースに任せます。ユースケースgetResult(), getKey()メソッドなど、取得データの処理を行ってもらいました。

これらの変更をコードに反映させると次のようになります。

<?php

use Illuminate\Contracts\Config\Repository as ConfigContract;

final readonly class ResultRepository implements ResultRepositoryInterface
{
    public function __construct(private ConfigContract $config)
    {
    }

    public function get(ResultKey $resultKey): ResultRepositoryGetOutput
    {
        $resultList = $this->getResultList();

        return new ResultRepositoryGetOutput($resultList[$resultKey->toString()] ?? null);
    }

    /**
     * @return array<string, Result> string is ResultKey
     */
    private function resultList(): array
    {
        $resultMaster = $this->config->get('Feature.resultMaster');
        $result = [];
        foreach ($resultMaster as $key => $value) {
            $result[$key] = new result(
               ...
            );
        }

        return $result;
    }
}

合わせてファイル読み込みのテストは次のようにモックできます。

<?php
$configMock = Mockery::mock(ConfigContract::class);
$configMock->shouldReceive('get')
        ->andReturn([...]);

これで問題になっていたテストしにくいファイル読み込みの処理をなくすこと、リポジトリーとユースケースの責任の分割が行えました。よりテストしやすく、変更に強いコードを書くことが出来ました。

最後に

今回のインターンで関わってくださった皆様に、この場を借りてお礼申し上げます。インターン期間中はメンターの方はもちろん、上長や話したいと希望した人との面談まで行ってくださいました。どなたも私の質問や説明に対して答えを返すだけでなく、理由や他の案も考慮に入れて応えてくださいました。就活やインターンの短い期間に限らず、社会に出た後のキャリアについてもとても参考になりました。

メンターやLife&Wellness事業部の方々のサポートによりインターンに参加した2つの目的は十二分に達成することが出来ました。目的を持ってこれができるようになろう、と参加するとそれ以上のものができるようになり、新しく課題も見つかります。エキサイトの就業型インターンは経験を積み、できることを増やしたい人にとてもおすすめです。ぜひ、申し込みましょう。

就業型インターンシップBooost!!!に参加して学んだこと

はじめに

こんにちは!エキサイト株式会社で1ヶ月の期間、就業型インターンシップ「Booost!!!」に参加させていただいた山根です。今回はインターンシップで取り組んだことや学んだことについてご紹介していきます。

自己紹介

私は現在情報系の大学に通っている学部3年の学生です。個人やハッカソンでwebアプリケーションを開発した経験はありますが、実務での開発経験はなかったため、夏休みの期間を利用して様々なインターンシップに参加をして経験を積みたいと考えていました。

その中で縁あってエキサイト株式会社のLife&Wellness事業部にインターン生として開発に携わらせていただきました。

インターンシップでの業務内容

Life&Wellness事業部でも様々なサービスがある中、エキサイト電話占いで使われているPHPフレームワークであるBEAR.Saturdayを最近主流のLaravelへ移行する業務に取り組み、その中でも無料占いページの移植タスクを行いました。

私はPHPでの開発経験がなかったため、初めはロジックを含まないタスクから割り振ってもらいました。2つのページの移植が終了したら、ファイルベースでデータの処理を行うタスクに取り組みました。このプロジェクトではレイヤードアーキテクチャが採用されており、リポジトリユースケースでロジックの分離を行っています。移植元とは構成が異なるため、メンターの方にサンプルを用意していただき、ディレクトリ構成についても詳しく説明してくださいました。また、PRのレビューも丁寧に行なってくださり、どこが悪いかだけでなく理由や改善案など提案していただけたり、良い点があれば評価もしてくださるため、高いモチベーションを保ったまま開発に取り組むことができました。

学んだこと

私は今回のインターンシップの目標として、実務での開発や会社の雰囲気について知るということを目標としていました。1ヶ月間インターンシップを通して様々なことを経験できましたが、中でもこれまでの開発でできなかったことや実務でのプロジェクトに参加したからこそ学べたと感じたことについてまとめていきます。

コーディング規則

部署内ではコーディング規則が決められており、タスクに取り掛かる前にまずこれを確認しました。特にPHPはPSRに準拠しており、理解が難しいものはありませんでした。記述方法に統一性がないと可読性が低く、変更容易性も失われる可能性が高いです。コーディング規則を決めておくことはチーム開発をする上で非常に重要だと感じました。

その点、本プロジェクトではCIが整備されており、規則に反している箇所を示してくれます。CIでの確認だけでなく、開発環境上でコマンドを叩くことで静的解析を行うこともでき、自動修正をすることまで可能です。短期間でコーディング規則を全て頭に入れることは難しいですが、これらのツールがあるおかげで、非常に開発が進めやすい環境だと感じました。

チーム開発

今回のインターンシップで最も学びが多かったのが、チーム開発になります。
ある実装において、実務でのチーム開発の考えが最も現れた部分について紹介します。

修正前

Laravelではbladeというテンプレートファイルを使用しています。今回のプロジェクトのアーキテクチャではフォーム入力から得た結果をbladeに表示するために、ViewModelでその結果を整形する必要がありました。 私は初め、bladeファイル内で変数を表示するために以下のようなコードを書いていました。

// blade
@section
    <p>{{ $vm->getResult()->getPersonality()->toString() }}</p>
@endsection

// ViewModel
<?php

final readonly class ResultViewModel
{
    public function __construct(
        private ResultIndexGetUseCaseOutput $useCaseOutput,
    ) {
    }

    public function getResult(): Result
    {
        return $this->useCaseOutput->getResult();
    }
}

このコードでも結果を表示することはできるのですが、いくつか問題点があります。

  • 変更容易性が低い

もしデータの取り出し元が変更になった場合、bladeでモデルクラスの実装をしていると修正箇所が広くなってしまいます。そのため、ViewModel内でモデルクラスの実装をまとめていた方が変更に対して強くなります。

  • bladeファイルはデザイナーも触れるため、処理を含まない方がいい

bladeファイルはユーザーの目に見える部分であるため、デザイナーが編集する可能性があります。そのため、bladeファイルに処理が存在する場合、思わぬ障害が発生しかねません。

修正後

先ほどの問題点を踏まえ、bladeではモデルクラスの実装を含まない、デザイナーもbladeに触れることがあるため処理を減らすということをポイントとし、以下のようなコードに修正しました。

// blade
@section
    <p>{{ $vm->getPersonality() }}</p>
@endsection

// ViewModel
<?php

final readonly class ResultViewModel
{
    public function __construct(
        private ResultIndexGetUseCaseOutput $useCaseOutput,
    ) {
    }

    public function getPersonality(): string
    {
        return $this->useCaseOutput->getResult()?->getPersonality()->toString() ?? '';
    }
}

このコードであればbladeでモデルクラスの実装をしないで済み、bladeファイルの変更容易性が高くなります。また、デザイナーへの配慮もできたコードとなっておりViewModelから何を取得しているかの意図が明確になります。

こういった変更容易性やデザイナーとの連携を意識した開発は実務でしか行えないことなので、非常に良い学びになりました。

コミュニケーション

エキサイト株式会社ではコミュニケーションツールとしてSlackを使用しています。インターン生はtimesチャンネルを持つことになっており、今回初めて自分のtimesチャンネルを持ちました。同じタイミングでインターン生として入社している方も頻繁にtimesへ投稿をしていたので、大変刺激を受けました。私も積極的にtimesを活用することでLife&Wellness事業部の方とコミュニケーションを取れるようになりました。

しかし、Slackやビデオツール上でメンターの方に質問をする際、私は説明が冗長になってしまったり、説明が分かりづらく相手に意図を汲み取ってもらっているということに気づきました。上長の方にこの悩みを相談したところ、結論ベースで物事を話すと相手に伝わりやすいというアドバイスを受けました。上長との面談以降、朝会やSlackでの報告はこれを意識して取り組むように心掛けました。まだまだコミュニケーションは自分の課題であるため、今回発見した課題を改善し、より自分からアクションを起こせるようにしていきたいと感じました。

まとめ

今回のインターンシップで関わってくださった皆様に、この場を借りてお礼申し上げます。実際のプロダクトの開発に携わることができ非常に貴重な経験を積むことができました。開発をするだけではなく、他の事業部のインターン生や新卒エンジニアの方との交流会であったり、人事や上長、CTOとの面談まで行うことができ、会社についても知る機会が多く非常に充実した1ヶ月となりました。

2名のメンターの方の手厚いサポートにより、様々な発見や学びのあったインターンシップとなりました。どんな質問でも回答してくださり、理由や手段など丁寧に説明してくださいました。エキサイト株式会社の就業型インターンシップ「Booost!!!」は様々な経験を積みたいという方に是非おすすめしたいインターンシップです。

Alpine.jsのx-modelで入力された値を参照する方法

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回は、Alpine.jsで、input要素などで入力された値を参照したい場合に有用なx-modelディレクティブをご紹介します。

x-modelとは

x-modelはAlpine.jsのディレクティブの一つで、これを使用するとinput要素に入力された値をAlpine.jsで扱えるデータとしてバインドできます。

基本的な使い方

まずは、基本的な使い方を単純な例でお示しします。

See the Pen x-modelの簡単な例 by AyumuSaito (@ayumusaito-excite) on CodePen.

<div x-data="{ inputedText: '' }">
  <input x-model="inputedText" type="text" placeholder="文字を入力" />
  <p>入力された値:<span x-text="inputedText"></span></p>
</div>
  1. x-datainputedTextを定義(空文字)
  2. input要素に付与したx-modelを使用して入力された値を格納
  3. x-textinputedTextの値を参照して文字列としてリアルタイムに出力

といった処理の流れです。

どんな使い道があるか

先の例では単純に入力された値を出力するだけでしたが、以下のようにバリデーションを視覚的に表したい場合にも使えます。(CodePenにぜひ触れてみてください)

See the Pen Untitled by AyumuSaito (@ayumusaito-excite) on CodePen.

<div x-data="{ inputedHandleName: '' }">
  <div>
    <label for="handlename">ハンドルネーム(4文字以上)</label>
    <input 
    x-model="inputedHandleName"
    type="text" 
    name="handlename" 
    id="handlename"
    pattern=".{4,}"
    />
  </div>
  <div x-show="inputedHandleName.length > 0 && inputedHandleName.length < 4">
      <p>ハンドルネームは4文字以上の必要があります</p>
  </div>
</div>

この例では、入力されたハンドルネームが4文字未満の場合は受け付けないバリデーションをinput要素のpattern属性を用いて設定しています。

しかし、通常はpattern属性はsubmit時に結果を返すため、ユーザーは自分が入力した値が条件にあっているかを一度送信してみないと分かりません。

入力すべき項目が多いほど、バリデーションエラーがあった場合に修正すべき項目を探す手間が増えてします。結果として、体験を損ね、フォームから離脱してしまうということも考えられます。

そこで、x-modelを使用して入力値を条件と照らし合わせて、違反がある場合にはエラー要素を表示することで、submitせずともリアルタイムでバリデーションエラー伝えられます。

ただし、この方法は、UIのためのバリデーションチェックに過ぎませんので、意図しないデータを受け取らないようにpattern属性と合わせて使用することを強く推奨します。

さいごに

今回は、Alpine.jsを使用している環境下で、input要素などで入力された値を参照したい場合に有用なx-modelディレクティブをご紹介しました。

Alpine.jsを使用している方の一助となれば幸いです。

ご精読ありがとうございました。

輪読会から始めるドキュメント整備への取り組み

はじめに

こんにちは!エキサイト株式会社のまさきちです。 開発チームで「ユーザーの問題解決とプロダクトの成功を導く エンジニアのためのドキュメントライティング」という書籍で輪読会を行いました。 輪読会を行うことで得た学びをシェアいたします。

輪読会を始めたキッカケ

以前、開発プロジェクトのふりかえり会でドキュメント整備に次のような課題があることが判明しました。

  • フォーマットやまとめ方が統一されていない
  • 機能によってはドキュメントが存在しない
  • ドキュメントを見つけるまでに時間がかかる

tech.excite.co.jp

カイゼンに向けてのネクストアクションとして、ドキュメントの書き方や運用方法を学び実践しよう!という話になりました。 学ぶ手法として輪読会を行う事に決まりました。

実践方法

最初に輪読会の目的や進め方を話し合い、ルール決めしてから始めました。

開催頻度

開発メンバー3人で、月曜日と金曜日の週2回で各回30分程度で行い1回の輪読会に対して1章読み進める形で行いました。

進め方

事前にMiroを使って思ったことや参考になりそうな事を付箋に書き出し、輪読会は共有と意見交換メインで進めました。 ある程度読み進めた段階で、アウトプットのために実践する会も設けました。 ファシリテーターや共有する順番も会ごとに変えるなどマンネリ化しない工夫をしました。

学び

ドキュメントを作る前に誰のどのような課題を解決したいかを明確にすることが重要で、まず読み手に対しての深い理解が必要だということを再認識しました。 現状の現場のドキュメントの状態と書籍で紹介されているドキュメントの書き方や管理方法について比較することで、課題や改善点について洗い出すことができました。 ドキュメントを書いた後フィードバックを求める文化が無かったので、これからは積極的にフィードバックを貰ってカイゼンし続けようと思います。

ふりかえり

輪読会のふりかえりも行いました。

good
  • 定期的に集まってみんなで考える時間が取れたのは思考が深まった
  • 漠然とした「わからないからドキュメント欲しい」を言語化できた
  • ジャーニーマップは大変だったけど、課題感をより把握できた
  • 読みと実践を両方やったところはより理解度が進んだ
more
  • 輪読会を終えるまで3ヶ月かかってしまい、中弛みを感じた
  • Miroだけだと決定事項や学びが後から追いづらいので議事録や一覧で確認できるドキュメントが欲しい
  • 必要最低限の章だけ読めば長引かずに済んだかも

アウトプット

新規ドキュメントを書くためテンプレートを作り、記述項目を合わせることでフォーマットを統一して欲しい情報が探しやすいようにしました。 主要機能のドキュメントを輪読会メンバーで1つ作成して、カイゼン後のドキュメント記述の実例を示すことができました。 今回の取り組みを事業部の開発チームに共有して、ドキュメントの運用を見直しました。

まとめ

輪読会をして終わりではなく、ネクストアクションに繋げてアウトプットできて良かったです。 ひとりで読み進めるより、チームメンバーの意見を聞けたり認識の共有ができました。 ドキュメント運用に対しての具体的な課題が浮き彫りになり、解決方法を考えてカイゼンして成果を出す事が出来ました。

今後も開発組織のプレゼンス向上のための活動をしていきたいと思います。

【VS Code】VSCodeで削除したブランチが、GitHubに反映されていなかった【GitHub】

はじめに

【こちらはVSCodeGitHub触りたての超初心者さんに向けた記事です】

エキサイト株式会社デザイナーの山﨑と申します。

今回は、VSCodeでブランチを削除した時に、GitHubに反映されない事について調べてみました。

経緯

毎回使い終わったブランチの削除は、VSCodeから行っていました。

しかし、VSCode上でブランチを削除してもGitHubに反映されないので、GitHubでも同じブランチを消すという作業を行っていました。

原因

VSCodeで削除したのは「ローカルブランチ」で、GitHubにあるのは「リモートブランチ」のため、VSCodeで「ローカルブランチ」を削除してもGitHubにその削除が反映されることはありませんでした。

なぜ今までこのシステムを理解せずいじっていたのか…😭 これまで切ったブランチは必ずマージしており、マージ後はリモートブランチが自動で削除されるため、気づきませんでした。

解決方法

VSCode上でリモートブランチを削除する場所はないのかと探してみたのですが、どうやらVSCodeのブランチ削除画面では、仕様上ローカルブランチしか削除ができないようです。

そのため、VSCodeでブランチを削除してGithubのブランチも同時に削除したつもりになっていると注意が必要です。

GitHub上にあるリモートブランチを削除するには、以下の二つの方法があります。

①ターミナルからgit push --delete origin branch_nameを実行する(*「branch_name」に削除したいブランチの名前を入れてください。)

GitHubの「Branches」から直接削除する

最後に

最後に、エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!

興味があればぜひ連絡よろしくお願いいたします!🙇

www.wantedly.com

アイコンで説明するUIはアクセシビリティに気をつける

こんにちは。エキサイトでデザイナーをしている齋藤です。

今回は、アイコンを用いて何かを説明しているUIはアクセシビリティに気をつけたいというお話をします。

アイコンで説明するUIとは

冒頭、「アイコンで説明するUI」とはどんなUIなのかを、具体例を用いてご説明します。

よくある例としては、以下のような比較表です。

アイコンを用いて説明しているUIの例

アイコンで利用可能/不可能を説明しています。直感的でわかりやすい見た目ですが、マークアップの仕方によってはハンディキャップのないユーザーにしか伝わらないUIになってしまいます。

ハンディキャップないユーザーにしか伝わらないマークアップ

先の例をマークアップしてみます。(前提として、アイコンにはアイコンフォントを使用しているとします)

<table>
  <thead>
    <tr>
        <th></th>
        <th>Aプラン</th>
        <th>Bプラン</th>
        <th>Cプラン</th>
    </tr>
  </thead>
  <tbody>
    ....
    <tr>
        <th>メールサポート</th>
        <td><span class="icon x-mark"></span></td>
        <td><span class="icon circle-mark"></span></td>
        <td><span class="icon circle-mark"></span></td>
    </tr>
    ....
  </thead>
</table>

見出し行にはtheadタグを用いていたりと適切なタグを用いているため、一見アクセシブルなマークアップに見えます。

どこに問題があるのか

アクセシビリティ上の問題はどこにあるのでしょうか。

それは、アイコンで説明をしているセルにあります。

<tr>
  <th>メールサポート</th>
  <td><span class="icon x-mark"></span></td>
  <td><span class="icon circle-mark"></span></td>
  <td><span class="icon circle-mark"></span></td>
 </tr>

この要素のアクセシビリティツリーは以下のようになります。

table
  row
    rowheader "メールサポート"
    cell
    cell
    cell

アイコンで説明をしているセルが空白として認識されてしまっています。

この状態では、スクリーンリーダーの読み上げがされないため、視覚にハンディキャップのあるユーザーが情報を入手できなくなってしまいます。

ハンディキャップのあるユーザーにも伝わるマークアップ

では、ハンディキャップのあるユーザーにも伝わるマークアップに修正します。

今回は、マルアイコンは「利用可能」、バツアイコンは「利用不可能」という意味がアイコンにあるため、それを明示するようにrole属性とaria-label属性を用いてマークアップします。

<tr>
  <th>メールサポート</th>
  <td><span role="img" aria-label="利用不可能" class="icon x-mark"></span></td>
  <td><span role="img" aria-label="利用不可能" class="icon circle-mark"></span></td>
 <td><span role="img" aria-label="利用不可能" class="icon circle-mark"></span></td>
</tr>

なにをしたのか

アイコンフォントを使用しているspanタグにrole属性とaria-label属性を付与しました。

まず、role属性ですが、これはタグに対して意味を持たせるために使用します。

HTMLのタグはそれぞれに意味を持っていますが、spanタグは意味を持たないタグです。

しかし、今回は「アイコン(画像)を表示する」という意味を持つため、role="img"を付与します。

次にaria-label属性ですが、これはタグにアクセシブル名を付与するために使用します。

role="img"で画像の意味を加えましたが、このままでは「画像」であることしか伝わりません。

どんな内容の画像なのか(=アクセシブル名)を示す必要があるため、マルアイコンにはaria-label="利用可能"バツアイコンにはaria-label="利用不可能"を付与します。

修正後のアクセシビリティツリーは以下のようになります。

table
  row
    rowheader "メールサポート"
    cell "利用不可能"
    cell "利用可能"
    cell "利用可能"

これで、アイコンを用いていて説明しているセルもスクリーンリーダーが読み上げられるようになりました。

さいごに

今回は、アイコンを用いて何かを説明しているUIはアクセシビリティに気をつけたいというお話をしました。

今回ご紹介した方法はWCAG達成基準 1.3.1を達成するための方法の一つとして、ウェブアクセシビリティ基盤委員会(WAIC)のページでも紹介されていますのでこちらも合わせてご覧ください。

ご精読ありがとうございました。

展示会の備品を色々作ってみた件2-2

こんにちは!エキサイトお悩み相談室でデザイナーをしているサヅカです。

前回に引き続き、展示会出展で作成したクリエイティブについて紹介させていただきます。 tech.excite.co.jp

スタッフTシャツ作成

とにかく作っていて楽しかったです。今回は初めての出展だったので、まずは覚えてもらうことを目的にデザインしました。

イベントということもあり、お堅くなりすぎないようにロゴは今まで使ったことがない「ONAYAMI」をローマ字にして遊びも入れました。

裏はQRコードを配置してウェルカム感を追加。

Tシャツデザイン案

完成したデザイン

ついに完成しました!

やはりポイントは看板でも使用しているイラストの部分でしょうか。「Tシャツかわいいですね!」とお客さんにも言っていただけたのがとても嬉しかったです。

ちなみにTシャツは納品までに2週間かかり、展示会前々日の到着になってしまいめちゃくちゃ焦りました。印刷会社にもよりますが、発注する際は早めの入稿がおすすめです。

展示会当日の様子

Tシャツの色が思いのほか濃くて背景の備品と差はありますが、目立っていたので結果オーライです。

まとめ

デザイナーが現場で手伝うことは少ないと思うのですが、同じく出展していた他社のブースを間近で見ることができてとても勉強になりました。 どの企業様も目を引く工夫をしていて「なるほど!」の連続でした。

パンフレットを受け取って下さった方々、お話を聞いて下さった方々、また「エキサイトさんだ!」「翻訳使っていたよ〜(懐かしい)」「本当に24時間365日相談できるんですか!?(本当です)」等々とたくさん声を掛けていただきありがとうございました!