Blog

ブログ

コンセプトにぎりが超重要!デザイン制作の裏側をご紹介

株式会社ジョーレンはシステム開発はじめ、デザインの制作についても承っております。

今回はデザインの制作についてフォーカスし、制作の際、大事にしていることをご紹介します!

・ジョーレンのデザイン実績 一例

デザインはコンセプトの認識合わせが超重要

超重要  と書きました。

はい、デザインの制作において最初から最後まで1mmもこぼすことなく一貫して超重要なのです。

何がそんなに超重要なのか。その根拠について今回は綴っていきます!

ヒアリングで大切にしていること

まず始めにクライアント様にヒアリング。ヒアリングする内容は、オリジナルのヒアリングシートのフォーマットが存在し、

これを元にクライアント様の傾向に合わせてカスタマイズしてヒアリングを行っていきます。

制作者にとって少しでも多く情報があることは、アイディアのヒントになり良き制作につながるため、聞けることはなるべくヒアリングするようにしています。

ちなみに雑談内容も制作者にとってはヒントになるのです!雑談って大事。

またクライアント様の雰囲気やオーラを生で感じ取るため、対面でヒアリングを行うこともあります。体感で得られることもあるのです!対面でのヒアリングだったら、その様子を写真撮影しその後デザイナーに共有することも臨場感が伝えられてよいですね。

ヒアリングした内容で疑問に思ったことはそのままにせず必ず解消させ、疑問が残らないようにします。

そしてこのヒアリング内容はコンセプトへと繋がります。

ベースカラーどうする?

さてここでは、ヒアリング項目の中から1つピックアップしてご紹介しましょう。

ヒアリング時に欠かせない「ベースカラー」の取り決めについて。

成果物の印象を左右するものになりますから、クライアント様の気持ちも気合もぐぐっと入る質問内容です。

カラーを決める時に役立つツールがこちら!

感性マッピングツールなるものがあり、

日本カラーデザイン研究所」が研究・開発した配色イメージスケールが活用できます。

http://www.ncd-ri.co.jp/image_system/imagescale.html

シンプルな例でたとえると、

  • 「クールなイメージにしたい!」だったら  青系
  • 「情熱的なイメージにしたい!」だったら  赤系

といったように色を決めていきます。

クライアント様がベースカラーを決めること迷っていたら、すかさずこの配色イメージスケールを用いて取り決めるとよいでしょう?

もし仮にクライアント様から「クールなイメージにしたい!」という希望があるのにも関わらず、

赤系のベースカラーを指定した際には、この資料を元に説明しましょう。説得力が増します!

サイトの骨子となるWFの作成

次にヒアリング内容を踏まえて、WF(ワイヤーフレーム)を作成します。

※WF(ワイヤーフレーム)とは:サイトに掲載する要素を整理し、サイトに見立てレイアウト化したドキュメントのこと

・以下はEC-CUBE社提供のWFサンプル

 

WFを作る時に心がけていることはこちら!

  1. コンセプト(ヒアリングシート)を踏まえた内容か?
  2. グルーピング等して、情報整理が的確にできているか?  
  3. プライオリティを意識した見せ方になっているか?
  4. なぜこの配置にしているか、裏付けが語れるか?
  5. SNS等、必要な外部リンクは漏れなく設置できているか?

 

ここで超重要で触れた「コンセプト(ヒアリングシート)を踏まえた内容か?」が出て来ました。

WFの作成者がコンセプトからブレたものを作成してはいけません。目指す方向を見誤ってはいけません。しっかり振り返りつつWFの作成を行いましょう。

デザインはあくまで手段に過ぎないため、達成したい目的(コンセプト)を正しく伝えられるようWF作りを行いましょう。

余談:

私はまず始めに手書きでWF作りを始めます。その方が思考のスピードが早く、アイディアもまとまりやすいためです。ツールからだとツールベースについつい設計が走ってしまいがちなのです。

実際、手書きから作成する人が多いようです!

 

用意するWFは、

  • ハンバーガーメニュー
  • スマホとPC  両ページ

を欠かさず用意しましょう。1つでも欠けるとデザイナーが迷ったり制作負荷がかかることがあるためです。クライアント様とすれ違いや、デザインにおいて出戻りが発生することもありますので、怠らず作成を行います。

またポイントとして、デザインに近いWFになるよう、写真画像等を反映したカラフルなWFを作ります。(私のポジションとして、ディレクターの立場で依頼しています)

なぜかというと、以下2つの思いがあるからです!

1)クライアント様ご希望のデザインに近いイメージでWFを確認してもらえるため

2)デザイナーにこのWFを超える最高のデザインを作ってほしいため

そしてクライアント様から了承取った上、

レイアウトはいじってよい前提でデザイナーに依頼します。

デザイン依頼。目指している山の頂上は同じか!?

ヒアリングシートを元にコンセプトを整理して、デザイナーにデザインを依頼するステータスを向かえました。

心がけている点は、クライアント様から受けた内容プラスアルファ、デザインの際に参考となりそうな情報をデザイナーに伝えることです。

それを受けてデザイナーから発信されるアイディアは

「ああ、こう来たか!」「なるほど、こういった表現もできるか」「わーそこまで熟考できていなかった」

と多くの気づきを得ることがあります。またデザイナーからこういった指摘を受けることは、次に活かす学びにも繋がります。

その後デザイナーからデザインがUPされた時、UIが更に磨きがかかって仕上がっていた時は感動です。

見やすさや情報整理具合が、レベルアップ!!

デザイナーの存在に頼もしさ(後光が見えるっっ)を感じる瞬間でもあります。

そしてこの時、必ずコンセプトからブレていないかチェックを行うことは欠かせません。

なぜって。

みんなで目指す山の頂上は同じだから!

です。

※この言葉、この後でも出てきます。

クライアントプレビューそしてFIX。ここで武器となるのは!?

デザインが仕上がったら、コンセプトを添えてクライアント様にプレビューを行います。

そしてデザインを見たクライアント様からフィードバックが届くステータスを向かえました。

届いたフィードバックを確認してみたら、

時には1からデザインを修正しなければならないフィードバックが届くことがあります(ガクブル。

しかし!こちらには強力な武器があります!!

エビデンスとして用いる武器、コンセプトの元となるヒアリングシート!!!

コンセプトからブレているフィードバックを受け取ったら、

必ずクライアント様とコンセプトに立ち戻って一緒に確認を行いましょう。

そこでコンセプトからズレているフィードバックをしていること、クライアント様が気づきます。

みんなで目指す山の頂上は同じだから!

そうです。

クライアント様は違う山の頂上を目指してしまっていたのです。

それゆえコンセプトと異なるフィードバックをしてしまったのです。

冒頭で超重要と書いた理由はここにありました。

コンセプトを握らずデザインすることは、目指す方向を共有せず山を登ってしまうことを表します。

想像してみただけで、恐怖ですね。

トラブルを招くだけで、クライアント様はじめチームメンバーも安心して一緒に山の頂上を目指すことができません。

認識合わせって超重要ですね?

さて今回はデザインを行うに当たって制作の裏側のお話をさせて頂きました。

株式会社ジョーレンはShopify構築、EC-CUBE構築案件においてもデザインから制作を承っておりますのでお気軽にご相談ください。具体的なデザインイメージがなくてもお客様と伴走してご提案することが可能です。

 

▼EC-CUBEの実績はこちら

https://www.ec-cube.net/product/cases/backnumber.php?search_partner_id=1431

 

バナー

LINQ の基本を理解しよう

LINQ とは

LINQ とは コレクション・XML・SQLなど様々なデータソースに対する検索・操作を行うもので、System.Linq を参照することにより提供される拡張メソッド群(標準クエリ演算子)ことを指します。
本稿では特に利用する機会の多いコレクション(IEnumeable)に対する LINQ to Object の解説を行います。

メソッド構文とクエリ構文

LINQ は拡張メソッドで提供されることから、当然コレクションのメソッドとして利用する(メソッド構文)ことが可能です。
また一部の標準クエリ演算子に対しては言語仕様としてキーワードが割り当てられており、それを用いることでSQLライクに記述する(クエリ構文)ことも可能です。

例)メソッド構文とクエリ構文
 ※本稿のサンプルコードではメソッドの引数としてラムダ式を使用します。

var source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// いずれも結果で得られるコレクションの型はIEnumerable<int>となり、
// 列挙することで { 1, 2, 3, 4, 5 } の結果(要素)を得ることができます。

// メソッド構文
// 処理しない値を取り出す場合はSelectを書略可能
var methodResult = source.Where(x => x <= 5);

// クエリ構文
// クエリ構文ではselectを省略不可
var queryResult =
    from x in source
    where x <= 5
    select x;

またクエリ構文で記述したコードはコンパイルを通して標準クエリ演算子に変換されますので、最終的に得られる結果は同一になります。
前述のコードから得られる実行形式のファイルを逆コンパイルした結果は下記となります。

// Program
using System.Collections.Generic;
using System.Linq;

private static void <Main>$(string[] args)
{
	int[] source = new int[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	IEnumerable<int> methodResult = source.Where((int x) => x <= 5);
	IEnumerable<int> queryResult = source.Where((int x) => x <= 5);
}

なお全ての標準クエリ演算子をクエリ構文で記述することはできないため、それらの機能が必要な場合はメソッド構文で記述する必要があります。
※メソッド構文とクエリ構文を混ぜて使用することも可能ですが、式を分けるかどちらかに統一したほうが可読性の面からも無難です。

基本的な使い方

Where によるデータの抽出と Select によるデータの選択・処理

Where メソッドでは、コレクションから条件に合致する要素を抽出することが可能です。
Select メソッドでは、コレクションの全要素に対して処理を行った結果を取得することができます。
Where メソッドと Select メソッドを組み合わせてコレクションの条件に合致した要素に処理を行った結果を取得する、といった用途で利用することが多いようです。
ここでは、数値のコレクションから条件に合致する要素を抽出し二乗したコレクションを取得する例を示します。

var source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// メソッド構文
var methodResult = source.Where(x => x <= 5).Select(x => x * x);

// クエリ構文
var queryResult =
    from x in source
    where x <= 5
    select x * x;

// 結果は { 1, 4, 9, 16, 25 } となる。

また、数値型のコレクションで利用する以外にも、オブジェクトのコレクションに対して処理を行うことも可能です。
例えば人物を定義するPersonクラスのコレクションから20歳以上の人を抽出して氏名をつなげた文字列を取得するといった処理の場合、下記のように書くことができます。
※本稿のサンプルコードではnullチェック等を行っていませんが、実際に利用する場合は例外が発生しないように条件式に気を付けましょう。

/// <summary>
/// 人物を定義するPersonクラス
/// </summary>
class Person
{
    /// <summary>名</summary>
    public string FirstName { get; set; }
    /// <summary>姓</summary>
    public string LastName { get; set; }
    /// <summary>年齢</summary>
    public int Age { get; set; }

    public Person(string firstName, string lastName, int age)
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
    }
}

/// <summary>
/// サンプルクラス
/// </summary>
class Sample
{
    static void Main()
    {
        var persons = new[] {
            new Person("Terrance", "Huff", 18),
            new Person("Deven", "Cyrus", 26),
            new Person("Dave", "Corbett", 53),
            new Person("Brion", "Shoebridge", 12),
            new Person("Terence", "Long", 31)
        };

        // メソッド構文
        var methodResult = persons.Where(x => x.Age >= 20).Select(x => $"{x.FirstName} {x.LastName}");

        // クエリ構文
        var queryResult =
            from x in persons
            where x.Age >= 20
            select $"{x.FirstName} {x.LastName}";

        // 結果は { "Deven Cyrus", "Dave Corbett", "Terence Long" } となる。
    }
}

GroupBy によるデータの組み分け

GroupBy メソッドでは、コレクションの要素から取得した値をもとにグルーピングを行い、グループごとにデータを抽出することが可能です。
下記のサンプルコードでは、前述のPersonクラスのコレクションを元にFirstNameの頭文字でグルーピングを行っています。

// メソッド構文
var methodResult = persons.GroupBy(x => x.FirstName[0], x => $"{x.FirstName} {x.LastName}");

// クエリ構文
var queryResult =
    from x in persons
    group $"{x.FirstName} {x.LastName}" by x.FirstName[0];

// 結果は、下記となる。
// Key 'T'
//   { "Terrance Huff", "Terence Long" }
// Key 'D'
//   { "Deven Cyrus", "Dave Corbett" }
// Key 'B'
//   { "Brion Shoebridge" }

OrderBy / OrderByDescending による並び替え

OrderBy メソッドは昇順、OrderByDescending メソッドは降順で、コレクションの要素の並び替えを行います。
クエリ構文の場合は orderby 句と ascending 句または descending 句の組み合わせで並び替える順序を指定します。
数値を降順で並び替える場合は、下記のようなコードになります。

var source = new int[] { 3, 5, 7, 1, 10, 8, 4, 6, 2, 9 };

// メソッド構文
var methodResult = source.OrderByDescending(x => x);

// クエリ構文
var queryResult =
    from x in source
    orderby x descending
    select x;

// 結果は { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 } となる。

最後に

今回は割と利用頻度が高いと思われるメソッドを簡単に説明しましたが、これらを組み合わせることで複雑なデータの抽出処理を可読性を上げつつ簡単に記述することができます。
ただし LINQ は気軽に使える反面、書き方によっては非常に大量のメモリを使うことがあるため、Web アプリケーションのように複数スレッドで同時実行されるようなプログラムで大量のデータを扱う場合は、問題がないか十分気を付ける必要があります。
適切な場所でうまく活用できるようにしましょう。

.NET コーディングのTips2選

初めに

ここ最近携わった案件でいくつか気になるコードを見かけましたので、多少なりとも改善できればとTips的なものを2つまとめてみました。
非常に初歩的な内容のため、今更言われなくても知ってるよ!という方はスルー推奨、初めて聞いた方は今後の参考にしていただければと思います。

foreachステートメント直前の要素数チェック

foreach の直前で要素数の判定を行っているコードを見かけますが、要素数が 0 の場合は何もせずブロックを抜けるため、要素の有無で処理分けが必要なければ判定は不要です。

例1:直前のif文で抜けるパターン

// このif文のブロックは不要
if (elements.Count == 0)
{
    return;
}
foreach (var element in elements)
{
    // 処理本体
}
return;

例2:if分のブロックとして処理するパターン

// このif文の判定は不要
if (elements.Count > 0)
{
    foreach (var element in elements)
    {
        // 処理本体
    }
}

いずれの場合も、 `foreach` のみ記述するだけで問題ありません。

foreach (var element in elements)
{
    // 処理本体
}

IEnumerableを実装するコレクションの要素存在チェック

IEnumerable を実装するコレクションに要素が存在するかどうかをチェックする際、 Count メソッドで取得した結果が 0 より大きいかで判定するコードを見かけますが、このメソッドは対象をカウントするために内部で全要素を列挙しているため、パフォーマンスがかなり悪くなります。
明確な要素数で判定する必要がない場合は Any メソッドを使うようにしましょう。

検証

0 から 999999999 までの数値を列挙したコレクションから 1000000 以上の要素が存在するかどうかを、Count と Any それぞれのメソッドで判定した場合の実行時間を比較します。

コード
using System.Diagnostics;

public class Example
{
    delegate bool CheckFunc(IEnumerable<int> list);

    /// <summary>
    /// 判定結果と処理時間を出力
    /// </summary>
    static void WriteResult(string name, CheckFunc func, IEnumerable<int> list)
    {
        Console.Write($"{name} : ");

        // 時間計測開始
        var sw = new Stopwatch();
        sw.Start();

        if (func(list))
        {
            Console.Write("要素が存在します");
        }
        else
        {
            Console.Write("要素が存在しません");
        }

        // 時間計測終了
        sw.Stop();

        Console.WriteLine($" : {sw.ElapsedMilliseconds} ミリ秒");
    }

    /// <summary>
    /// 0から999999999までの整数を列挙
    /// </summary>
    static IEnumerable<int> GetValues()
    {
        for (var i = 0; i < 1000000000; ++i)
        {
            yield return i;
        }
    }

    public static void Main()
    {
        // コレクションを取得
        var array = GetValues();

        // 拡張メソッドを利用して要素を抽出
        // このコードでは1000000以上の値を抽出
        var result = array.Where(x => x >= 1000000);
        // 次のようにクエリ式で書くことも可能(現場によってはNGかも)
        // var result = from x in array where x >= 1000000 select x;

        // 結果に要素が存在するかどうかを判定
        // Anyメソッドの場合
        WriteResult("Any", x => { return x.Any(); }, result);
        // Countメソッドの場合
        WriteResult("Count", x => { return x.Count() > 0; }, result);
    }
}
実行結果
Any : 要素が存在します : 23 ミリ秒
Count : 要素が存在します : 18756 ミリ秒

処理にかかる時間は環境によって変わりますが、同じ判定結果を得るのにかなりの差が出ることを確認できます。

また、抽出した要素を再利用しない場合はメソッドチェーンで簡潔に書くこともできます。

if (array.Where(x => x >= 1000000).Any()) {
    // 処理
}

簡潔にパフォーマンスを考慮したコードを書くように意識したいですね。

次回は

今回出てきた IEnumerable に関連して LINQ の解説やTipsをまとめてみたいと思います。

Shopifyで構築。「DMMLunaサイト」をリリース!

合同会社DMM.com様(以降、DMM様)のご依頼により女性向けに検査キットを販売するサイト、

DMMLunaサイトを2022年10月31日にリリースいたしました。

オリジナルデザインで、Shopifyで構築した案件になります。

https://dmmluna.com/

 

DMMLunaブランドを確立すべく世界観をどう表現するかを熟思

今回DMM様の新たなサービスの試みとして、本サイトを立ち上げることとなりました。

課題として承ったミッションは、DMMLuna独自のブランディングがいかに確立できるかです。

その実現のため、いくつかの試行錯誤を行いデザインに望みました。

扱う商材をどう見せるか!?議論

まず課題に上がったことは商材をどう見せるかについてです。

本サイトで扱う検査キットとは、以下のような商材になります。

この商材写真をいくら飾っても、目指すデザイン感にマッチさせることはできません(;_;)

そしてチームメンバーとディスカッションした末、検査キットの写真は用いず、ターゲットとなる女性写真を主役に、検査項目の数を明示して見せる!

という案で世界観を壊すことなくまとめ上げることができました。

この案に行き着くまで他のアイディアも出ており、例として

「イラスト化する案は?」「商品画像の更新性負荷や、商材の正確な再現性が劣るのでは?」といった理由により見送りになった経緯も背景にあったのです。

・FIXした商材画像が見れるページはこちら!

https://dmmluna.com/collections/kit

 

ロゴへ込めた思い&ストーリー性を持ったキャラクター設定

次にオリジナルの世界観の実現です。

ベンチマークとなるサイトは、そのサイト独自に描き起こしたイラストや撮り下ろしの写真が起用されていました。

本案件ではイラストレーターの起用が予算等の兼ね合いにより厳しく、条件として担当デザイナーができる範囲で制作しなければなりません。

またセンシティブな内容を扱った商材となるため、サイト用の写真撮影も見送る必要がありました。

その制約ある中で、ミッションとしてDMMLuna独自の世界観を表現する必要があったのです。

更にDMM様がベンチマークとなるサイトを大変気に入っていたため、それを凌駕するサイトを作らなければ!!という課題もありました。

そして提案させて頂いた内容は以下になります。

  • DMMLunaが描くストーリー展開

    (メインビジュアルにおいて)

    ・ルナ先生の元に本サイトのターゲットとなる女性をイメージしたイラストを配置。

    ・お家は、自宅で検査することができる。

    ・ポストは、ユーザーが検査した検査キットを郵送する。

    ・傘の子は、安心を届ける。

    といったメッセージを込め構成。

 

  • サイトロゴ

    シンボルの形状から「優しく包む」を表現。

    全体の形はハートを想起させ 「心」「優しさ」「安心」を。

    内側に包む形状はDMMLunaが女性の健康を守り、 優しく包み安心をもたらすという意味をかたどる

 

  • コンセプト

    DMMLunaをきっかけに新しい価値観や新たな生活習慣の場を本サイトを通じてユーザーに与えられるよう、DMMLunaブランドを確立できるようデザイン。

    今までターゲットとなるユーザーが利用していたサイトはなんとなく気持ちがのらない、面倒くさいといったネガティブなイメージを与えていたかもしれません。

    DMMLunaではそういったイメージを変え、DMMLuna独自の世界観を演出した本サイトで一新を目指す。

    ※このコンセプトは、これからの顧客満足度の向上、そして市場シェアの拡大にもつながることも目的といたしました。

 

ご提案した世界観はDMM様に評価いただき、仕上がったデザインを通じてご要望に沿った世界観を作ること結実できたのです。

 

「よくあるご質問」の見せ方を思案する

ここでは制作エピソードのこぼれ話を。

よくあるご質問は一般的に以下のような文字ベースの印象が多いかと思います。

今回DMM様から

「文字文字の印象ではなく、キャッチーで読みやすい見せ方にしたい!」とご要望を受けておりました。

実現に向け、イラストを入れる、URLの文字列をそのまま記載しない等

チームメンバーと工夫はしてみたものの、テキスト原稿のボリュームがある程度あると、実現することは困難なことが分かりました。

(本サイトは制約上、これ以上テキスト原稿の文字数を減らすことはできませんでした)

そして仕上がったページはこちら!

https://dmmluna.com/pages/faq

とはいえ、一般的な「よくあるご質問」ページより読みやすくキャッチーな印象に仕上がっていますよね★

Shopifyで構築することで運用性UPの恩恵を享受

まだまだShopifyを勉強中の私。

今回Shopify構築を通していくつか気づきがありました!

Shopify構築をクライアント様に提案する際に、メリットとしてお伝えできるかと思います。

管理画面でカンタンに更新ができる

今回Shopify側で用意されているテーマ*を元にオリジナルデザインの適用を行いました。

Shopifyではオリジナルのデザインを適用しても、管理画面からGUIで更新できる(ソース更新ではなく、ビジュアルベースで更新できる)仕組みが作れます。

 

Shopifyで構築すれば、運用者側でソース更新することなく、制作者側に依頼かけずとも

ブログのようにGUIで更新できる!のです。

つまりShopifyを使うと、手間なくノンテックの方でもとても簡単に運用する体制が築けます。

テーマとは:デザインのテンプレートのようなもの。テーマをそのまま使い構築もできますし、テーマを元にオリジナルデザインを作ることもできます。

戦略に合わせて、コンテンツの順序を並び替えられる

クライアント様のご要望として「更新頻度が高いコンテンツを上部に配置したい」という声が寄せられます。

例えば、「ブログコンテンツ」や「お知らせ」等々。

一方でストーリー展開を意識した場合、初めてユーザーに見てもらうサイトの場合、順序としてまずは自己紹介から入りますよね。

「私は◯◯です。◯◯◯をやっているサイトです。よろしく!」

このサイトはなんぞや的な紹介ですね。

この構成を優先とした場合、ページ最上部に配置することが適しています。しかし時が経ちサイトリリース後、リピーター層が多くなった場合は、ユーザーにとって何度も見なくてもよいコンテンツになるため、下に配置変更したくなる時が訪れるかもしれません。

この点Shopifyでは柔軟に応えることができます!

先程紹介した管理画面のGUIで上下に移動させ変更することが容易にできるのです。

つまり、サイトの戦略に合わせてコンテンツの配置をいつでも簡単に変更できるので叶えられるのです。

構築のポイントは、

配置を変更しても自然な見え方になるようデザインする!です。

今回紹介した以外でもShopifyが得意としていることはたくさんありますので、構築の検討をされている方はお問い合わせ頂き、ご質問いただけたらと思っております。

 

株式会社ジョーレンではオリジナルのデザインから、Shopifyのテーマ(テンプレート)を使った構築まで、業種ジャンル問わず幅広く承っております。

「こういった機能を付けたい」等、機能面に関するご相談も承ること可能ですので、お気軽にご相談ください。

EC業界トップクラスの実績!自社ECサイト構築が398,000円から
EC業界トップクラスの実績!自社ECサイト構築が398,000円から

 

【ECCUBE4系】MemcachedにのせたDoctrineCacheのCacheClearコマンドを自作してみる

今日はcacheのお話です。

※今日は、大好きなEventSubscriberの出番はありません※

前置き

複数台構成でECCUBEを運用するときに、気になるのはcacheですよね!

cacheの恩恵は大きいですが、サーバーが複数台構成の時は困ってしまうことも。。。

今回は、3台構成のWebサーバーでDoctrineCacheをMemcachedで管理するときに、DoctrineCacheのクリアコマンドを自作したときのお話です。

DoctrineCacheをどうやってMemcachedにのせるの。。。?という内容は、ご要望があれば別日に書こうかと思ってます。

なので、今回はDoctrineCacheをどうやってMemcachedにのせる設定とかは割愛させて頂きます。

 

実際に作成したコマンド

app/Customize/Command配下に以下のファイルを作成しました。

<?php

namespace Customize\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class DoctrineCacheClearCommand extends Command
{
    protected static $defaultName = 'memcached:doctrine-cache:clear';

    const SESSION_PREFIX = 'session';

    /**
     * @var SymfonyStyle
     */
    protected $io;

    protected function initialize(InputInterface $input, OutputInterface $output)
    {
        $this->io = new SymfonyStyle($input, $output);
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            $memcached = new \Memcached();
            $memcached->addServer(env('MEMCACHED_HOST'), env('MEMCACHED_PORT'));
            $keys = $memcached->getAllKeys();

            foreach ($keys as $key) {
                if (substr($key, 0, 7) !== self::SESSION_PREFIX) {
                    $memcached->delete($key);
                }
            }

            $this->io->success('Doctrine Memcached All Clear.');
        } catch (\Error $e) {
            $this->io->error('Doctrine Memcached Clear Failure Error.');
        } catch (\Exception $e) {
            $this->io->error('Doctrine Memcached Clear Failure Exception.');
        }

        return true;
    }
}

処理の説明

今回はsessionもMemcachedにのせているので、Memcachedをflush_allするというわけにはいきませんでした。

session情報も消えてしまうので、releaseする度にCustomerが再ログインしなければならないなんて、ユーザビリティ悪すぎですもんね。。。

 

方法はとてもシンプルです!

Memcachedから全てのkeyを取得して、sessionのプレフィックスがついているもの以外を順次削除していくだけです!

本当は。。。

DoctrineCacheにプレフィックスを作成して、そのプレフィックスのkeyを取得して消すという方法を取りたかったのですが、DoctrineCacheの時はプレフィックスをつけられなかったのです。。。

まだまだ勉強不足なんでしょうね。。。

 

使い道

管理画面からキャッシュクリアを行うときに合わせて呼び出すように改修したり。。。

デプロイ用のスクリプトに組み込んでみたり。。。

サーバーで直接叩いてみたり。。。

と、使っています。

サーバーが複数台構成でも、cacheとは上手く付き合っていきたいですよね、やはり画面の表示速度が違います!

 

最後に

今回は自作でキャッシュクリアのコマンドを作ってみた時のお話でした。

最近は画面側を作成するより、外部連携等のBatch作成の方が楽しいなーなんて思ってます。

この機会に、サーバーが複数台構成でもcacheと上手くお付き合いできる方法を考えてみませんか?

【ECCUBE4系】CSV登録画面初期表示で、twigマスタテーブルの値を表示させる

今日もtwigのお話を書いていこうと思います。

※今日は、大好きなEventSubscriberの出番はありません※

前置き

例えば、商品CSV登録画面の「公開ステータス(ID)」は、messages.ja.yamlが空文字になっているので、説明欄には何も出力されていないですよね?
商品の公開ステータスは、mtb_product_statusはデフォルトの状態で3レコードしかないので、messages.ja.yamlに直接記載する方法でもそんなに手間ではないかもしれません。

ですが、これが
「mtb_pref(都道府県のマスタテーブル)のIDと名称を全部出力してください。」
なんて依頼が来ると、47レコード分をmessages.ja.yamlに記載するのは正直手間だなー。。。と思ってしまいました。

そこで、テーブルからidとnameを抜き出して、文字列化するServiceを作ったので、そのご紹介になります。
テーブル構造によっては、mtbでもdtbでも使えるので、試してみてください。

 

前提条件

  • primary keyがIDであること
  • 出力する値のカラム名がnameであること

 

Serviceを作成する

まずは、Serviceを作っていきましょう。

<?php

namespace Customize\Service;

class CsvViewDiscriptionService
{
    /**
     * CSV登録画面でマスターデータから選択肢を表示させる文字列を返却
     * 例)1:北海道 2:青森県 3:岩手県
     *
     * @param array $data
     * @return string|null
     */
    public function getDiscriptionTextData(array $data)
    {
        $text = null;
        foreach ($data as $key => $datum) {
            if ($key !== 0 && $key % 5 === 0) {
                $text .= '<br>';
            }
            $text .= $datum->getId() . ':' . $datum->getName() . ' ';
        }

        return $text;
    }
}

ServiceはこれだけでOKです。
引数をforeachで回して、文字列に変換しているだけですね!

 

Controllerで呼び出す

次にControllerの方で、このServiceを呼び出してみましょう。
必要箇所のみ抜粋して記載してみます。

trans('admin.product.product_csv.display_status_col') => [
    'id' => 'status',
    'description' => 'admin.product.product_csv.select_id_description',
    'required' => true,
    'data' => $this->csvViewDiscriptionService->getDiscriptionTextData($this->productStatusRepository->findAll())
],

今回は商品ステータスを出力させたいので、productStatusRepositoryから全件取得を行なっています。
もちろん、ここは条件付きでも問題ありません。
要は、twigで描画したいレコードが取得できれば良いのです。

 

twig側の修正

<div id="ex-csv_product-format" class="card-body">
    <table class="table table-striped table-bordered">
        <tbody>
        {% for header, key in headers %}
            <tr>
                <th class="w-25 align-middle table-ec-lightGray" id="file_format_box__header--{{ loop.index }}">{{ header }}
                    {% if key.required %}
                        <span class="badge badge-primary ml-1">{{ 'admin.common.required'|trans }}</span>
                    {% endif %}
                </th>
                <td class="align-middle">
                    {% if key.description %}
                        {{ key.description|trans|raw }}
                    {% endif %}
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
</div>

こうなっている箇所を

<div id="ex-csv_product-format" class="card-body">
  <table class="table table-striped table-bordered">
    <tbody>
    {% for header, key in headers %}
      <tr>
        <th class="w-25 align-middle table-ec-lightGray"
            id="file_format_box__header--{{ loop.index }}">{{ header }}
          {% if key.required %}
            <span class="badge badge-primary ml-1">{{ 'admin.common.required'|trans }}</span>
          {% endif %}
        </th>
        <td class="align-middle">
          {% if key.description %}
            {{ key.description|trans|raw }}
          {% endif %}
          {% if key.data is defined %}
            <hr>{{ key.data | raw }}
          {% endif %}
        </td>
      </tr>
    {% endfor %}
    </tbody>
  </table>
</div>

こんな感じで修正します。

discriptionを出力している箇所に

{% if key.data is defined %}
  <hr>{{ key.data | raw }}
{% endif %}

が追加されただけですね。

動作確認

最後に動作確認をしてみましょう。

商品ステータス(ID)の箇所が以下のように出力されていると思います。

1:公開 2:非公開 3:廃止

これで、テーブルから取得してきた値をtwigにStringで渡すServiceの完成です、とっても簡単ですね!

 

最後に

前提条件で、「primary keyがIDであること」と「出力する値のカラム名がnameであること」と記載しましたが、自由にカスタマイズ可能だと思います。
(文章があまり上手くないので、説明しやすくしたかったのです)

こんなServiceを1つ作っておくだけで、簡単に管理画面のCSV登録画面での文言出力が簡単になります。
この機会に、CSV登録画面にテーブルの値を出力してユーザービリティーを向上させてみませんか?

【ECCUBE4系】フロント側の全twigで参照できる変数を作る

こんにちわ。
今回はtwigの変数についての投稿です。

twigで値を参照したい場合、通常であればControllerで値を取得し、returnしてtwigに渡しますよね?
でも、sessionやcookieに格納している値を全画面で利用したいなーと思うと、全画面のControllerとtwigの修正を行うとなると、それなりの工数がかかりますよね。

例えば、Customerがログインを行なった場合、認証が通った後で会員の今までに購入した金額によって

  • 今までの購入金額が1万円未満の場合は、Aグループに所属するCustomer
  • 今までの購入金額が5万円未満の場合は、Bグループに所属するCustomer
  • 上記以外の場合は、Cグループに所属するCustomer

と言った具合で条件を設定し、それでtwig側で出力する文言を変更したいです!なんて要望があった場合、数箇所であればControllerから値を渡すでもいいと思いますが、フロント側全体で文言の出しわけをして欲しいという要望があると、twigの修正は仕方ないですが、Controllerも併せて全修正するのは大変です。

そこで、今回も出てきます、EventSubscriberです!
みんな大好き、EventSubscriberです!

 

ログイン時の処理を作成する

今回は一例で紹介させて頂きますので、詳しいコードは割愛します。

ご担当の案件の要望に合わせて、customer_groupをキーにsessionにお好みの値を保持してください。

 

twigで参照する変数を作る

ここからが今日の本題です。

まずは、以下のようなコードをapp/Customize/EventListener配下に作成してみましょう。

 

<?php

namespace Customize\EventListener;

use Eccube\Request\Context;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;

class TwigInitializeListener implements EventSubscriberInterface
{
    /**
     * @var bool 初期化済かどうか.
     */
    protected $initialized = false;

    /**
     * @var Environment
     */
    protected $twig;

    /**
     * @var Context
     */
    protected $requestContext;

    /**
     * TwigInitializeListener constructor.
     *
     * @param Environment $twig
     * @param Context $context
     */
    public function __construct(
        Environment $twig,
        Context $context
    ) {
        $this->twig = $twig;
        $this->requestContext = $context;
       
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        if ($this->initialized) {
            return;
        }
        if ($this->requestContext->isFront()) {
            $this->addGlobal($event);
        }

        $this->initialized = true;
    }

    public function addGlobal(GetResponseEvent $event)
    {
        $customerGroup = $event->getRequest()->get('customer_group');

        $this->twig->addGlobal('customerGroup', $customerGroup);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::REQUEST => [
                ['onKernelRequest', 7],
            ],
        ];
    }
}

これで、twigから参照する処理は記載できました。
今回は、TwigInitiializeListenerで認証処理が完了した後に実行したかったので、Priorityは7に設定してあります。

 

twig側で参照する

ここまでできたら、あとはtwig側に処理を書くだけです。

{% if customerGroup == 'A' %}
    <div>
                <p>今月末までに1万円以上買っていただけると、お得意様になります。</p>
    </div>
{% elseif customerGroup == 'B' %}
        <div>
                <p>今月末までに5万円以上買っていただけると、超お得意様になります。</p>
        </div>
{% else %}
        <div>
                <p>あなたは現在、超お得意様になります。</p>
        </div>
{% endif %}

これで、全twigでcustomerGroupが参照できるようになっているはずです。
もちろん、Controllerが用意されていない静的ページからでも参照できます。

EventSubscriber便利ですね!
みんな大好き、EventSubscriberです!!!(2回目)

twigに渡す変数、各Controllerから渡す前に、EventSubscriberで使い回す道を検討してみませんか?

Requestのお供に、EventSubscriberをぜひよろしくお願い致します。

【ECCUBE4系】ボタンで表示言語を選択できるようにする

先日shopifyでサイトを多言語化する方法をご紹介させて頂きました。
本日は、ECCUBE4系で多言語化対応を行う際に、ボタンで言語を切り替えられるように実装してみたのでご紹介します。

まずはControllerの作成

<?php

namespace Customize\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;

class LanguageController extends AbstractController
{
    /**
    * @Route("change/language", name="language_change")
    */
    public function changeLanguage(Request $request)
    {
        $session = $request->getSession();
        $session->set('language', $request->query->get('language'));

        return $this->redirect(言語選択後に表示したいページ);
    }
}

これでボタンを押したときに、セッションに選択した言語情報を保持出来るようにしました。

 

header.twigに言語切り替えボタンを出力

{% if app.session.get('language') == 'ja" %}
  <ul>
    <li><a href="{{ url('language_change', {'language': 'en'}) }}">{{ 'English'|trans }}</a></li>
  </ul>
{% else %}
  <ul>
    <li><a href="{{ url('language_change', {'language': 'ja'}) }}">{{ '日本語'|trans }}</a></li>
  </ul>
{% endif %}

選択済みの言語によって、出力されるボタンが動的に変わるようにしました。
(初期値は、.envのECCUBE_LOCALEに設定されている値なので、「English」のボタンが初期値で出力されます)

EventSubscriberを作成する

ここが一番大好きな作業です!
みんな大好き、EventSubscriberです!

<?php 
namespace Customize\EventSubscriber; 

use Symfony\Component\DependencyInjection\ContainerInterface; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
use Symfony\Component\HttpKernel\Event\GetResponseEvent; 
use Symfony\Component\HttpKernel\KernelEvents; 

class LanguageSubscriber implements EventSubscriberInterface 
{ 
    /**
     * @var ContainerInterface
     */ private $container;

    /**
      * LanguageSubscriber constructor. 
       * 
       * @param ContainerInterface $container 
       */ 
       public function __construct(ContainerInterface $container)
      { 
                 $this->container = $container;
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::REQUEST => [['onKernelRequest', 18]],
        ];
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        $request->setLocale($request->getSession()->get('language', env('ECCUBE_LOCALE')));
    }
}

これで出来上がりです!
なんて簡単なんでしょう、EventSubscriber最高ですね!

最後に補足説明

ちなみに、KernelEvents::REQUESTで、2番目に指定している「18」ですが、これはSymfonyのPriority(優先順位)の指定になります。
ECCUBEもローカルで開発してる時なんかは、デバッグモードにしていると思いますが、デバッグバーをポチッとするとSymfonyの画面に遷移しますよね?

左のメニューバーから「Events」を選択すると、Event Dispatcherが確認できるので、
“Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest()”
のPriorityが幾つになっているか確認して、LocaleListener::onKernelRequest()よりも優先順位を決めてあげるのが良さそうです。

優先順位まで指定できるなんて、EventSubscriber最高ですね!(2回目)
EventSubscriberが大好きすぎて、いろんな処理を割り込ませたくなりますが、他の処理を割り込ませた話は、またの機会に投稿しようと思います。

messages.XX.yamlやvalidators.XX.yamlの作成を忘れずに行ってくださいね!

Requestのお供に、EventSubscriberをぜひよろしくお願い致します。

Shopifyサイトを多言語化する方法【基礎編】

Shopifyで多言語化する方法

 

こんにちはJoolenECチームです。

本日は、Shopifyで多言語化サイトを構築するにあたって、そもそもの根本的な情報をまとめました。

具体的な多言語化方法は後日執筆予定です。

それではご覧ください。

言語と地域の概念おさらい

まず多言語化とは切っても切れない「他地域化」とい考え方についてご紹介します。

言語と地域は別物

始めに押さえておきたいのは、言語と地域は別ということです。

日本なら大多数が日本語を話しますが、アメリカは人種も言語も多種多様です。

そこで重要なのが以下の2つです。

  • 何語を話す方に向けて販売するのか(言語)
  • どこの地域の在住者に向けて販売するのか(地域)

例えば、韓国在住のアメリカ人向けに韓国料理をネット販売したい場合、Shopify側で「言語=英語、地域=韓国」と設定する訳です。

そうすると、英語話者が韓国国内から「Korean food」とGoogle検索すると、そのShopifyサイトが検索結果に出る、という流れです。

さらにサイトにアクセスする、英語のページが表示され、ウォンで決済し、届け先は韓国国内、といったイメージです。

如何でしょう。イメージ湧きましたでしょうか?

Googleがサイトの言語と地域を判別する方法

Googleは検索者が何語を話してどこにいるかをIPアドレス等で把握しています。

では、Webサイトが「何語を話すどこにいる人を対象としているのか」はどのように把握しているのでしょう?

様々な方法で把握しているようですが、概ね以下のものがあります。

  1. 国別トップレベルドメイン(例 joolen.jp、joolen.us)
  2. Google Search Consoleで設定したターゲット地域
  3. HTMLまたはsitemap.xmlに記載されたhreflang
  4. サイトの住所やTEL、他ローカルサイトからのリンク
  5. サーバーの場所

Shopifyでサイト構築する際は、主に「2.」と「3.」によって言語/地域を明示します。

Google以外の検索エンジンの国別シェア

これまでGoogleの言語/地域把握方法を紹介してきましたが、各国の検索エンジンシェアはどのようになっているのでしょうか?

2021年、国別検索エンジンシェア

米:Google88%、Bing6%
欧:Google93%、Bing3%
露:Google55%、Yandex42%
中:Baidu73%、Sogou17%
韓:Google80%、Naver13%

出典:検索エンジンの国別シェア【2021年版】

中国・ロシアを除いて、Googleの対策をすればある程度の人口が対象となります。

Google以外の対応方法は、それぞれのウェブマスターツールで個別対応が必要です。

まとめ

以上が多言語化と多地域化の概要となります。

次回は具体的な多言語化方法、多地域化方法、おすすめのアプリ、翻訳はどうすれば良いか、などをご紹介します!

楽しみにお待ちください!

 

EC業界トップクラスの実績!自社ECサイト構築が398,000円から
EC業界トップクラスの実績!自社ECサイト構築が398,000円から

【Shopify】ネットショップの管理画面をわかりやすく説明します(商品管理編)

こんにちは。
JoolenECチームです。

 

いきなりですが、ネットショップを始めたい、と思った時に、

  • やってみたいけど、自分で操作できるかな?
  • どんな画面でどんな操作をするんだろう?

という不安がある方もいらっしゃるんじゃないでしょうか。

 

実際に「ネットショップ 制作」などで検索してみると、ある程度慣れている方向けの情報だったり、ボリューミーな記事が多くヒットしました。

もちろん、慣れてくればそういった記事もとても参考になると思うのですが、

まずはシンプルで、わかりやすい説明の記事があっても良いのではないか、と思いました。

 

そこで今回は、ネットショップが初めての方向けに、Shopifyの実際の管理画面をお見せしながら、ご説明したいと思います。

商品管理編 その1です!

 

管理画面を見てみる

何はともあれ、管理画面を見てみましょう。

なんだか色々書いてありますね。

今回は商品管理編なので、商品管理を選択します。

 

商品管理画面へと移動しましたね。

 

商品管理画面について

商品管理画面とは、Shopifyで取り扱う商品情報を管理する画面です。
その他、ギフトカードの発行もこちらから行います。

 

初期状態では何も商品が登録されていないので、このような画面が表示されます。

 

商品を登録してみよう

今回は、「商品を追加」ボタンをクリックして、試しに商品を登録してみましょう。

 

すると、登録したい商品の情報入力画面に移動します。

まずは画面を見てみましょう。

 

うーん、項目、いっぱいありますねえ・・

実際に運用する際は、商品ごとに販売や管理上必要な項目を入力するのですが、

今回はお試しですので、最低限の項目を入力して商品を登録してみましょう。

イメージ重視です。

 

 

こんな感じで登録して・・・

 

実際のショップ画面はどんな感じか見てみると・・・

 

 

こんな感じで表示されました!!!

 

補足:実際に商品を登録する場合は?

今回はお試し版として最低限の項目のみ登録していますが、実際には商品のタイプやコレクション(カテゴリのようなもの)、タグなどを登録したり、商品のバリエーション情報(色やサイズなど)なども登録することが可能です。

登録する商品が多い場合は、csvファイルを使ってまとめて登録することもできます。

 

まとめ

いかがでしたでしょうか?

今回の記事を通じて、ちょっとでもネットショップを身近に感じていただけたら嬉しいです!

 

 

最後に宣伝です。

Joolenでは、ShopifyをはじめとしたEC制作を行なっています。

質問やご相談だけでももちろんOKですので、ネットショップ開設でお悩みの際は

ぜひお気軽にご連絡ください。

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