Blog

ブログ

【Eccube3系】キャッシュクリアコマンドでプラグインのキャッシュが消えないお話

EccubeではtwigやDoctrieで使用されているキャッシュをクリアする方法が、大きく2つあります。

  1. 管理画面でキャッシュクリア
    • コンテンツ管理のキャッシュ管理 で削除対象をチェックしてクリア
  2. キャッシュクリアコマンドでクリア
    • 3系だと下記コマンドですね
      • php app/console cache:clear
      • ※このコマンドを実行する際は、適切なユーザーで行う必要があります

私の参画していた案件ではコードがデプロイされると自動的にキャッシュクリアコマンドが実行されたので、(2)の方法でキャッシュクリアをしていたことになるのですが、それだとPluginのキャッシュがクリアされませんでした。

ということでキャッシュクリアコマンドでPluginのキャッシュもクリアさせる方法です。

  • 参考
    • https://github.com/EC-CUBE/ec-cube3/blob/3.0/src/Eccube/Util/Cache.php
/src/Eccube/Util/Cache.php



public static function clear($app, $isAll, $isTwig = false)
{
    $cacheDir = $app['config']['root_dir'].'/app/cache';

    $filesystem = new Filesystem();
    if ($isAll) {
        $finder = Finder::create()->in($cacheDir)->notName('.gitkeep');
        $filesystem->remove($finder);
    } elseif ($isTwig) {
        if (is_dir($cacheDir.'/twig')) {
            $finder = Finder::create()->in($cacheDir.'/twig');
            $filesystem->remove($finder);
        }
    } else {
        if (is_dir($cacheDir.'/doctrine')) {
            $finder = Finder::create()->in($cacheDir.'/doctrine');
            $filesystem->remove($finder);
        }
        if (is_dir($cacheDir.'/profiler')) {
            $finder = Finder::create()->in($cacheDir.'/profiler');
            $filesystem->remove($finder);
        }
        if (is_dir($cacheDir.'/twig')) {
            $finder = Finder::create()->in($cacheDir.'/twig');
            $filesystem->remove($finder);
        }
        if (is_dir($cacheDir.'/translator')) {
            $finder = Finder::create()->in($cacheDir.'/translator');
            $filesystem->remove($finder);
        }
        // pluginディレクトリを対象に追加ここから
        if (is_dir($cacheDir . '/plugin')) {
            $finder = Finder::create()->in($cacheDir . '/plugin');
            $filesystem->remove($finder);
        }
        // pluginディレクトリを対象に追加ここまで

    }

    if (function_exists('opcache_reset')) {
        opcache_reset();
    }

    if (function_exists('apc_clear_cache')) {
        apc_clear_cache('user');
        apc_clear_cache();
    }

    if (function_exists('wincache_ucache_clear')) {
        wincache_ucache_clear();
    }

    return true;
}

カスタマイズ箇所は「pluginディレクトリを対象に追加ここから」「pluginディレクトリを対象に追加ここまで」とコメントしている間の4行のみです。(コメント含めると6行)

「キャッシュクリアコマンドで全てのキャッシュファイルは削除される」と思い込んでいましたが、デフォルトではPluginは対象外のようです。

ちなみに、--allをつけてphp app/console cache:clear --allで実行するとapp/cache配下全てがクリアされるので、Pluginのキャッシュファイルも削除されますが、これは稼働中のサーバでは実行してはいけません。
デフォルトではapp/cache配下にセッションファイルも格納しているためです。

cacheというディレクト名なので削除可能なファイルが置かれている印象を受けますが、実はセッションのようなファイルが入っている、特にカスタマイズしている場合はセッション以外にも削除不可なファイルが配置されている可能性もありますので、allオプションはよほど自信が無い限りは利用しないほうが良いと思います。

以上、「キャッシュクリアコマンドで全てのキャッシュファイルは削除されると思ったらプラグインは別だった」でした。

【ECCUBE4系】商品価格を税込みで登録したい場合のカスタマイズ

ECCUBEでは、商品の価格は税抜の本体価格を登録する仕様になっています。

しかし元々税込価格で管理していた場合、端数の処理を考慮しつつ税抜価格を計算して登録しなければなりません。

それならはじめから税込価格を登録したい、というケースで実施したカスタマイズ方法がこちらです。

内容としてはシンプルに、商品の税表示区分はすべて「税込」とし、外税額を求めるメソッドで必ず0を返すようにしてしまうというものです。(確認したECCUBEバージョンは4.2です。)

app/Customize/Service/OrderHelperExtension.php を作成し、OrderHelper の getTaxDisplayType を上書き宣言します

<?php

namespace Customize\Service;

use Eccube\Entity\Master\TaxDisplayType;

class OrderHelperExtension extends \Eccube\Service\OrderHelper
{
    /**
     * 税表示区分を取得する.
     * 価格を税込みで登録するため、常に"税込"を返す
     *
     * @param $OrderItemType
     *
     * @return TaxDisplayType
     */
    public function getTaxDisplayType($OrderItemType)
    {
        return $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::INCLUDED);
    }
}

app/Customize/Service/TaxRuleServiceExtension.php を作成し、TaxRuleService の getTax を上書き宣言します

<?php

namespace Customize\Service;

class TaxRuleServiceExtension extends \Eccube\Service\TaxRuleService
{
    public function getTax($price, $product = null, $productClass = null, $pref = null, $country = null)
    {
        // 価格を税込みで登録するため、外税計算は常に0を返す
        return 0;
    }
}

app/Customize/Resource/config/services.yaml でサービスの上書きを設定します。

services:
  Customize\Service\OrderHelperExtension:
    autowire: true
    decorates: Eccube\Service\OrderHelper
  Customize\Service\TaxRuleServiceExtension:
    autowire: true
    decorates: Eccube\Service\TaxRuleService

以上で、商品に登録した価格を税込みとして扱うことができます。

【注意】プラグインや独自カスタマイズで、上記で上書きしたメソッドを使わずに税額・税込価格を計算している場合があります。動作確認を行い、適切な修正を施す必要があります。

 

 

【ECCUBE3系】既存プロジェクトにPHPStanを導入して、コード品質向上しよう!

プロジェクトが大規模になるにつれて、コードの複雑性や保守性の向上が課題となります。このような課題に対処する手段として、いろいろなツールがあると思いますが、今回はPHPStanをプロジェクトに組み込むことで、コードの品質向上させたいと思います。

PHPStanの導入

PHPStanをプロジェクトに導入するために、まずComposerを使用して必要なパッケージをインストールします。

composer require --dev phpstan/phpstan

場合によっては下記のエラーが出るかもしれません。

Generating optimized autoload files> Illuminate\Foundation\ComposerScripts::postAutoloadDump> @php artisan package:discover –ansi
In PackageManifest.php line 122: Undefined index: name

このエラーはcomposer v2を使用している場合に発生するようです。
なので、一旦composerのバージョンを下げることで解決できます。

composer self-update --1

インストールが完了したら、プロジェクトのルートディレクトリに .phpstan.neon という設定ファイルを作成します。このファイルには、PHPStanの静的解析のレベル設定や解析対象となるディレクトリを指定します。

parameters:
    level: 8
    paths: src/Eccube/
    excludePaths: src/Eccube/Command/GeneratorCommand/*

これで準備が整ったので、一度PHPStanを実行してみましょう!

vendor/bin/phpstan analyze

すると、大量にエラーが…!

PHPDoc tag @param has invalid value \\(\\$value\\)\\: Unexpected token \”\\$value\”, expected type at offset 102$PHPDoc tag @param has invalid value \\(\\$value\\)\\: Unexpected token \”\\$value\”, expected type at offset 18PHPDoc tag @param has invalid value \\(\\$value\\)\\: Unexpected token \”\\$value\”, expected type at offset 57Parameter \\#1 \\$str of function trim expects string, string\\|null given\\.

8000件くらいエラーが出てしまいました。。既存のプロジェクトに途中から導入しているので仕方ないですね。
こういう時のために、PHPStanでbaselineというものを作成できます。baselineに含めたエラーは一旦無視できます。

vendor/bin/phpstan analyze --generate-baseline

`phpstan-baseline.neon`が自動生成されますので、こちら`phpstan.neon`に記載し読み込んでもらいましょう。

phpstan.neon
includes:
    - phpstan-baseline.neon

parameters:
    level: 8
    paths: src/Eccube/
    excludePaths: src/Eccube/Command/GeneratorCommand/*

これで大量のエラーは一旦無視できました!

型注釈の追加

エンティティやそれに関連するメソッドに型注釈を追加します。これにより、PHPStanはコードの型整合性を確認しやすくなります。

// src/Entity/YourEntity.php

class YourEntity
{
    /**
     * @var string
     */
    private $name;
    
    // ...

    /**
    * @param string
    * @return string
    ** /
    public function test(string $message)
    {
        return message;
    }
}

 

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

PHPStanはコードの静的解析を行い、型の整合性や潜在的な問題を見つけてくれます。これにより、コードの品質向上や開発プロセスの効率化が期待できます。


PHPStanを導入することで、より安全で信頼性の高いコードを構築することができます。是非一度試してみて、プロジェクトのメンテナンス性や開発速度の向上を実感してください。

【eccube2系】マイグレーションツールのPhinx導入したい!2

前回、phinxをインストールしたところまでを説明しましたので、
今回は実際に使用してDBを更新してみたいと思います。

マイグレーションファイルを作成したり、実行する場合は下記のディレクトリで行います。

cd {dataのパス}/vendor/bin

まずはマイグレーションファイルを作成してみましょう!
ファイル名は見た感じわかりやすい方がいいですね。

今回はメンバーに誕生日カラムを追加してみましょう!

php phinx create AddDtbMember

下記のようなものが表示されればOKです。

Phinx by CakePHP – https://phinx.org. version 0.10.8

using config file ./phinx.yml
using config parser yaml
using migration paths
– {dataパス}/data/db/migrations
using seed paths
– {dataパス}/data/db/seeds
using migration base class Phinx\Migration\AbstractMigration
using default template
created {dataパス}/db/migrations/20231030045812_add_dtb_member.php

{dataパス}/db/migrationsディレクトリにファイルができているはずです。

change()メソッドが生成されているので、下記のソースを追加しましょう。

$this->execute(“alter table dtb_member add birth datetime null comment ‘誕生日’;”);

changeメソッド以外にも色々メソッドはあるので、こちらを参考に使い分けてもらってもいいと思います!
https://book.cakephp.org/3/ja/phinx/migrations.html

マイグレーションの実行をしてみましょう!

php phinx migrate -e development

マイグレーションが実行されていれば下記の表示がされます!

Phinx by CakePHP – https://phinx.org. version 0.10.8

using config file ./phinx.yml
using config parser yaml
using migration paths
– {dataパス}/db/migrations
using seed paths
– {dataパス}/db/seeds
using environment development
using adapter mysql
using database eccube2_db

== 20231030045812 AddDtbMember: migrating
== 20231030045812 AddDtbMember: migrated 0.2129s

All Done. Took 0.2522s

ちなみに、、、
下記のコマンドで実行前のSQLの確認もできますが、初回実行前に試したらphinxlogができておらずエラーになってしまいました。。。

php phinx migrate -e development –dry-run

PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘{DB名}.phinxlog’ doesn’t exist in {dataのパス}/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php:194

phinxlogはマイグレーションファイルを管理しているテーブルになります。

CREATE TABLE `phinxlog` (
`version` bigint(20) NOT NULL,
`migration_name` varchar(100) DEFAULT NULL,
`start_time` timestamp NULL DEFAULT NULL,
`end_time` timestamp NULL DEFAULT NULL,
`breakpoint` tinyint(1) NOT NULL DEFAULT ‘0’,
PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

ちなみに今回実行したマイグレーションの情報はこんな感じで保存されていました。

version,migration_name,start_time,end_time,breakpoint
20231030045812,AddDtbMember,2023-10-30 14:25:13,2023-10-30 14:25:14,0

実際に私が担当した案件でphinxを導入したら格段にマイグレーションの管理がしやすくなりました!!

ぜひ2系を触る場合は初期構築時に導入することをお勧めします!!

 

【eccube2系】マイグレーションツールのPhinx導入したい!

eccube2系を複数人で開発しているときに思うこと。

「3系とか4系みたいにマイグレーション機能があればいいのに!!」

無くてもできるとは思いますが、他メンバーが作ったSQLを自分でいちいち叩くのは面倒。
しかもどのSQL叩いたかどこかにメモしておかないと忘れちゃう…

なんてことありますよね。

それを解消するために今回は2系にマイグレーションツールの「Phinx」を導入したいと思います。

まず、ローカル環境でdocker+eccube2を用意してください。

ここからphinxの導入をしたいと思います。

(1)composerからphinxをインストールする

composer require robmorgan/phinx

しかしエラーが発生。。

Cannot use robmorgan/phinx’s latest version 0.15.2 as it requires php-64bit >=8.1 which is not satisfied by your platform.
Using version ^0.14.0 for robmorgan/phinx
./composer.json has been updated
Running composer update robmorgan/phinx
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

Problem 1
– Root composer.json requires robmorgan/phinx ^0.14.0 -> satisfiable by robmorgan/phinx[0.14.0].
– robmorgan/phinx 0.14.0 requires symfony/console ^3.4|^4.0|^5.0|^6.0 -> found symfony/console[v3.4.0, …, v3.4.47, v4.0.0, …, v4.4.49, v5.0.0, …, v5.4.28, v6.0.0, …, v6.3.4] but the package is fixed to v2.8.52 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.

Use the option –with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
You can also try re-running composer require with an explicit version constraint, e.g. “composer require robmorgan/phinx:*” to figure out if any version is installable, or “composer require robmorgan/phinx:^2.1” if you know which you need.

Installation failed, reverting ./composer.json and ./composer.lock to their original content.
# php composer require robmorgan/phinx
Could not open input file: composer

ふむふむ。。
phpとバージョンが合わないから使えないよー。
明示的にphinxのバージョンを指定してね的なことを言われました。
なので下記で再実行

composer require robmorgan/phinx:*

ちゃんとインストールされました!

(2)Phinxの設定ファイルを生成するためvendor/bin配下に移動

cd {dataのパス}/vendor/bin
php phinx init

そうすると /var/www/data/vendor/bin/phinx.php(Phinxの設定ファイル)が生成されます。

(3)Phinxの設定ファイルを編集する。

①migrationsを変更→{dataパス}/db/migrations
②seedsを変更→{dataパス}/db/seeds
③developmentもローカル環境に合わせて修正
※data/config/config.phpを参考

paths:
migrations: ①
seeds: ②

environments:
default_migration_table: phinxlog
default_database: development
production:
adapter: mysql
host: localhost
name: production_db
user: root
pass: ”
port: 3306
charset: utf8

development:③
adapter: mysql
host: localhost
name: development_db
user: root
pass: ”
port: 3306
charset: utf8

testing:
adapter: mysql
host: localhost
name: testing_db
user: root
pass: ”
port: 3306
charset: utf8

version_order: creation

(4)ディレクトリを作成する

mkdir {dataパス}/db
mkdir {dataパス}/db/migrations
mkdir {dataパス}/db/seeds

これで構築完了です。

一旦ここで終わりです。

次はマイグレーションファイルを実際に作って実行してみましょう。

【ECCUBE4系】よくある質問 〜支払方法設定〜

今日はECCUBE4系の管理画面の操作で、お客様から1番頂く質問について書いていこうと思います。

1番多い質問。。。それは、支払方法設定について!!!

「管理画面の支払方法設定画面で、支払方法を追加したのですが、フロント側で商品の購入手続き画面に出てこないのですが、不具合でしょうか。。。?」
そうですよね、そう見えますよね。。。

ですが、それは不具合ではないのです!
ECCUBE4系の立派な(?)仕様なのです!

まず、下記スクショがデフォルトの状態の支払方法設定画面ですね。

ここに、新しい支払方法を追加します。

ちゃんと表示で登録できていることが確認できました。ではフロント側の購入手続き画面の支払方法を確認してみましょう!

あれ。。。出てこない。。。

登録したはずの支払方法「後払い」が出てこない。。。

これは。。。不具合!?

いえいえ、不具合ではございません!

配送設定画面というのが、管理画面に存在するのはご存知でしょうか?

はい、「取り扱う支払方法」の項目に、新しく追加した「後払い」が選択できるようになっていますが、設定がされていないことにお気づきでしょうか?

ここで、購入手続き画面をもう1度見てみましょう。

「配送方法」の項目はサンプル業者が選択されていますね。

ですが、サンプル業者の設定では、支払方法に「後払い」が設定されていないのです。

そのため、購入手続き画面でお支払方法に追加した「後払い」が選択できないという事象が起こったのです。

サンプル宅配を選択してみると、「銀行振込」しか出力されていないですね。

これは、管理画面の配送方法設定のサンプル宅配が、「銀行振込」しか設定されていないためです。

このように、配送業者によって支払方法を設定できるため、支払方法設定画面で支払方法の追加を行っただけでは、購入手続き画面の「お支払方法」に出力されないというわけです。

支払方法設定画面は、支払方法の選択肢の設定が行える画面

配送方法設定画面は、配送方法ごとに詳細を設定できるので、支払方法の設定も行える画面

と、覚えておくと良いかもしれません。

なんでこうなるのかな〜?がわかっていないと、意外とハマってしまう支払方法設定の解説でした。

支払方法設定で困っている方のお役に少しでも立てれば幸いです。

「CUSTOMIZE EXPERT&CREATIVE EXPERT」ダブル受賞!!|BRAVO MUSIC オンラインストア

去る2022年5月30日に、BRAVO MUSIC オンラインストアをEC-CUBEで構築しリリースいたしました。

本サイトはEC-CUBE社から、「CUSTOMIZE EXPERT」&「CREATIVE EXPERT」を授与されております。

ダブルで受賞した実績は、弊社初です!!!(パチパチパチ???)

エンジニアチーム&デザインチームの汗と涙の結晶となった案件です?

https://bravomusic.jp/

 

 

さて本リニューアルでは、以下EC-CUBE2系の2サイトを1サイトに統合し、リニューアルすることになりました。

 

このような多くの情報を整理し、まとめ上げたのは私自身初めての試みでした。

「こんなに多い情報どうする〜〜」と嘆きの声が私一瞬ありましたが、とてもやりがいがありハートが熱くなるお仕事となりました★

情報整理に精を出す

全て洗い出して情報整理!

本プロジェクトの趣旨。2サイト(「fitness musicサイト」と「yoga musicサイト」)を1サイトに統合するミッション!

2サイトある上、両サイトとも、とても情報が多いサイトであり、また整理されていないコンテンツもある状態でした。そのためまずやったこと!2サイト分のページを全て洗い出す作業を実施!!

Excelに全ページ、リストアップし、その後ブラボーグループ様にリプレイス先では必要なページかどうかをご判断頂きました。その上でWFを作成し、優先順位が高いものは、グルーピングやカテゴリとして位置づけし情報整理に努めました。

カテゴリ名や配置場所について熟考を重ね、ブラボーグループ様と対話しながら現在の内容で確定。

そして優先順位が低いものは、「フィットネス関連コンテンツ」&「ヨガ関連コンテンツ」としてスライドバナーエリアを設け集約することにしました。

ジャンル分けしきれないものや、コンテンツとして優先度が低いものはここにまとめ、情報整理することに努めたのです。(2023.02現在では、この部分はクローズさせています)

「お知らせ」については下の方に配置することにしたため、重要なトピックはファーストビューとなるメインビジュアル下で訴求エリアを確保。バナー点数を際限なく設置できるよう、スライドバナーを展開する見せ方でご提案いたしました。

これなら「お知らせ」エリアが下に配置されていても、しっかりと重要なトピックスをユーザーにお知らせすることができます!

 

さて1つトピックスをお話します。

ジャケ写下にある「視聴する」ボタン。きっと思うことがあるでしょう!

ボトムをそろえたい!!!!!(みんなの声が聞こえる〜〜)

一度ボトムをそろえたことがあるのですが、不自然に間のあいた空白が出きてしまい、そろえることをやめました。全て同じ文字数なら、きれいにレイアウトがまとまるでしょうが、動的部分になるためその選択肢は取れません。

一方で商品一覧ページでは、「視聴する」ボタンをそろえています。

本ページは整然としたページ内容となるため不自然さがなく、そろえる選択を取りました。ページの見せ方によって臨機応変にですね!

https://bravomusic.jp/products/list?product_type_id=1

メニューの見せ方はシンプルに!

今回譲れない点がありました。それは、ヘッダー周りのメニュー導線をシンプルに使い勝手よい導線とすること。ログイン時も気を配りこの点妥協せず、担当デザイナーと協議しました。

加えて「著作権について」アテンションをかけたいこと、ブラボーグループ様から要望を受けたため、ヘッダー周りが煩雑にならないよう、目に付きやすい赤ベタで配色。今回追求したシンプルさを叶えるため、スクロール時の追従からは外すことにしました。追従なしでもファーストビューに配置することで目的を達成できていたことも理由です。

ドロップダウンメニューも活用し、その中で多くのメニューを展開し整理することにしました。

検索も検索窓をトップページに表示せず、ドロップダウンメニュー内に展開することでコンパクトに見せる効果を発揮しています。

と同時にスマホのハンバーガーメニューについてもシンプルな設計にこだわりました。

全てメニューを表示すると縦にとても長くなり、ユーザーにとって使い勝手の悪いものになってしまいます。そのため、クリックしたら下位のメニューが展開する見せ方でブラボーグループ様からも了承いただくことができました。

(ブラボーグループ様もこの部分の見せ方について気にされており、解消できてよかったです!)

 

 

 

ここでときおり話題になるトピックを!ページトップについて。

スクロール時に追従させているサイトもありますが、「そこまで必要?」と思うことがあります。スマホだと特に、操作性も落としてしまう導線だと考えています。実際この「ページトップ」導線を使っているユーザーも少ないことでしょう。

iPhoneだとステータスバーをタップし、この動作を取るユーザーも多くいること想像しますよね。現在、「ページトップ」の導線がないサイトも存在しているくらいです。

そのため「ページトップ」は、フッターのデザインアクセント程度の役割とみなしてよいと考えています!実はこの「ページトップ」デザイン、メインビジュアルでもアクセント処理として扱われていることがあります。矢印の向きは ⬇ です。ぜひ他サイトでこの処理を探してみてください★

タブをフル活用!

今回タブが、情報整理に貢献しています。

 

そこでも一番複雑なタブ構成が「オススメ楽曲ベスト3」!

情報整理の仕方について一番悩んだコンテンツでもあります。スマホではプルダウンで選択する形式を取り、使いやすいUI設計を目指しました。

ただ!この設計はバックエンド側の処理工数について検討する必要がありました。今回はエンジニアと相談しこの設計を実現することができました★

こういった時の事前の対策として、WFの時点で&デザインが仕上がったタイミングでエンジニアに問題がないか前もって確認を取りながら進めるようにしています。

デザインはトレンド感を取り入れる

デザインリニューアルするからには、トレンド感を取り入れたい。デザイン制作に携わる者にとっては、ごく自然な考えです。それでは本サイトで取り入れたトレンドについてピックアップし、ご紹介します。

▼背面全面に写真を配置し、レイヤー構造を採用

その上をコンテンツがスクロールしていくことで、スクロール時に奥行きが加わり、スケール感ある迫力あるイメージを効果として与えています。

▼ブロック単位で横に流れる文字

デザインのアクセントになるだけでなく、リッチ感も演出してくれます。英字は配置するだけで華やかさが加わり、装飾として多用されています。

(この英字の装飾方法は使いやすくもあるので、安易に使うことなく上手に取り入れたいところです!)

この2つのデザイン処理について、デザインギャラリーサイトを見ていると同じような表現をしているサイトをいくつか見かけるかと思います。実はデザインって、基本パターン化して作られているのです。

似通ったサイトに見えないよう処理を組み合わせて、オリジナルのエッセンス等を取り入れデザインしていることが分かります。じゃあ誰でもトレンド感あるデザインが作れるんじゃないかって??

それは一朝一夕ではできません。が回答です★

余白感や細部のディティール、フォントについてもベストを尽くして完成させているためです。心地よいデザインにたどり着くまで何度も試行錯誤を重ねます。

ディレクターである私もラフデザインを作ることがあり、身を持って知りました〜〜!機会があったらみなさんもデザイン制作にチャレンジしてみてください♪

チームメンバー一丸で作ったサイト!

最後に本サイトの制作メンバーについて触れさせてください♪

本サイトはチームメンバー一丸となって作り上げ、思い出深いサイトとなりました。制作中はお互い遠慮することなく必要あらばみんなで集まって、すり合わせや進捗確認を行いました。リモートメンバーが多いため、そんな時はSlackのハドル機能を使って柔軟に集まります!

チームワークって大切!!

リリース日はみんなでオフィスに一同に集まり、追い込みました。オフィスにいた他メンバーも気にかけてくれて、応援してくれたりその場を和ませてくれたり?

22時前にオフィスに届いた出前(ビルの表の入り口が閉まっていて、出前のお兄さんが辿り付けず!予定より配達時間が遅くなってしまったのです 笑)をみんなで囲んだことは今でも良い思い出です。←ちなみにレアなことです。めったにありません!!22時はオフィスに誰もいないことが基本である弊社。

リリースできた時はみんな感極まり、喜びの声が上がりました!感動!!!私も嬉しかったー。ついに我が子が世にリリースされた気持ちです✨✨

今現在もブラボーグループ様がこまめに更新して下さり、サイトを育てている様子を見るととても嬉しく思います。

 

株式会社ジョーレンは「EC-CUBEインテグレートパートナー」ページで、プラチナランク企業として掲載されています。他EC-CUBE構築実績も掲載していますので、ぜひご覧ください!

https://www.ec-cube.net/integrate/partner/

https://www.ec-cube.net/integrate/partner/partner.php?partner_id=1431

【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をぜひよろしくお願い致します。