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

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

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

 

 

【shopify】初回のみ表示するポップアップ

個人情報同意などに使える、サイト内のどのページからでも初回だけ表示されるポップアップを作りたいと思います。
全ページなのでtheme.liquidに直接書く方法もあると思いますが、簡単に管理できるように新規セクションで作成してみます。

セクションを新規作成

カスタマイズボタンの横にある3点ボタンから、「コードを編集」を開きます。
sections>「新しいセクションを追加する」で空のセクションを作成します。

liquid編集

まず、jquaryを読み込ませます。CDNまたはassetsに必要なファイルを入れて読み込んでください。
次にCSSを追加します。このセクション内だけに適用されればいいので直接書いちゃいます。

{%- style -%}
  .popup {
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.6);
    position: fixed;
    z-index: 1001;
    top: 0;
    left: 0;
  }
  .popup-content {
    width: 75vw;
    max-width: 750px;
    height: 40vh;
    background: white;
    border-radius: 4px;
    padding: 3rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
  }
  .close-btn {
    background-color: black;
    border-radius: 2px;
    color: white;
    text-align: center;
    cursor: pointer;
    font-size: 1.2rem;
    padding: 1rem 2rem;
    display: block;
    max-width: 10rem;
    margin: 1rem auto;
    width: 100%;
  }
  .close-btn:hover, 
  .close-btn:focus {
      text-decoration: none;
      cursor: pointer;
      opacity: 0.8;
  }
  @media screen and (max-width: 768px){
    .popup-content{
      width: 80vw;
    }
  }
{%- endstyle -%}

そしてポップアップ本体です。

<div id="popup" class="popup">
  <div class="popup-content">
    <div>{{ section.settings.popup_text }}</div>
    <span class="close-btn">はい</span>
  </div>
</div>

今回は、localStorageでユーザーが訪問済みなのか否か判断させます。さらに「はい」のボタンクリックによって、visitedtrueになるようにしました。

<script>
  const keyName = 'visited';
  const keyValue = true;
  if (!localStorage.getItem(keyName)) {
      $('#popup').css('display', 'block');
      $('.close-btn').click(function(){
          $('#popup').css('display', 'none');
          localStorage.setItem(keyName, keyValue);
      });
  } else {
      $('#popup').css('display', 'none');
  }
</script>

最後にスキーマです。表示されるテキストはカスタマイズ画面から編集できるようにリッチテキストにします。これで改行や太字、リンクもできるので便利です。

{% schema %}
{
  "name": "ポップアップ",
  "class": "pop-section",
  "settings": [
    {
      "type": "richtext",
      "id": "popup_text",
      "label": "テキスト"
    }
  ],
  "presets": [
    {
      "name": "ポップアップ"
    }
  ]
}
{% endschema %}

セクションを設置

あとはカスタマイズ画面でセクションを差し込めば完成です。
特定のページのみであればテンプレートごとに設置しますが、全ページに適用したいので共通のヘッダー内に入れました。

このようなセクションが1つできれば、Cookieにしたり、販促案内やクーポン用にカスタマイズの幅が広がりそうです。

参考:https://into-the-program.com/execution-firsttime-access/

使用テーマ:Dawn 15.1.0

関東ITソフトウェア健康保険組合に加入しました

株式会社ジョーレンは、

2024年9月に「関東ITソフトウェア健康保険組合(ITS)」に加入しました。

 

かねてより会社独自の施策として、

インフルエンザ予防接種費用補助などを実施してきましたが、

ITSへの加入により、より総合的で充実した健康支援制度を提供できるようになりました。

 

メンバーの家族(被扶養者)への施策が手厚くなった点も、うれしいポイントです。

 

今後もメンバーが健康に、より長くジョーレンで働き続けられるよう、

社内制度の充実に努めて参ります。

 

念願の加入に、社長もにっこにこ。

 

【shopify】ヘッダーのハンバーガーメニューの配置を変更したい

テーマのデフォルトでは左側にハンバーガーメニューが配置されているけど、右側に置きたい。。
しかし、カスタマイズ画面ではロゴの配置くらいしか設定できない。。
ということで、今回はshopifyのDawnテーマ(15.1.0)でできるだけ簡単に実装したいと思います。

liquidで物理的に動かしてみる

そもそもヘッダーセクションではソースコードの兄弟要素が「ハンバーガーメニュー・ロゴ・アイコン」という順番で書かれているため、header.liquid内の該当箇所を入れ替えてみました。

ソースコード上は理想の順番になりました。
しかしブラウザを確認しても、あれ、変わりません。。

 

CSSをチェック

ヘッダーセクションの中身が大きく3つに分かれており、flexで並べられています。
base.cssで順番が指定されていました。navigationを一番右に移動させます。

CSSだけでハンバーガーメニューの位置を変更できました!
でも開閉が左のままで不自然なので調整が必要みたいです。

 

Base.css

.menu-drawerに以下を追加

transform: translateX(100%);
left: auto;
right: 0;

 

component-menu-drawer.css

.js details[open].menu-opening>.menu-drawerに以下を追加

transform: translateX(0%);

 

 

配置も挙動も理想の動きになりました。

Liquidファイルを編集しなくても、CSSで簡単にカスタマイズできることがわかりました。

 

参考:

・CSS: カスケーディングスタイルシート[justify-self]

https://developer.mozilla.org/ja/docs/Web/CSS/justify-self

・shopifyコミュニティ「Re: モバイル表示のハンバーガーメニュー位置を右に表示したい。(テーマ:Spotlight)」

https://community.shopify.com/c/%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AAq-a/%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB%E8%A1%A8%E7%A4%BA%E3%81%AE%E3%83%8F%E3%83%B3%E3%83%90%E3%83%BC%E3%82%AC%E3%83%BC%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E4%BD%8D%E7%BD%AE%E3%82%92%E5%8F%B3%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%9F%E3%81%84-%E3%83%86%E3%83%BC%E3%83%9E-spotlight/m-p/2646855

キックオフナイト2024

株式会社ジョーレン管理部のKanaです。

 

3月22日に昨年も利用させていただいた、

Link松戸店さんにて今年度のキックオフを開催致しましたのでご報告します。

ジョーレン社員やパートナーさんなど、総勢40名!!

遥々京都から参加したメンバーも。

 

さてさて、まずは社長・副社長からのお話でスタートです。

普段は社内にて行う全体MTGですが、この日はこちらで和やかに行われました。

普段と場所は違えど、一同真剣な顔つきです。

業績・今年度の予算・MVPの発表などなどがありました。

今期も社員一丸となって精進して参ります。

 

全体MTGも終了したところで

お次は社長の合図でお待ちかねの「乾杯~」です。

ソフトドリンクで乾杯の方も多かったです。

私はもちろんビール片手に乾杯~何杯飲んだかな?笑

皆さんの表情も和みます~

今回は飲み放題・ビュッフェ形式でお願いしました。

手作りのお食事が並びます。

写真を撮り忘れてしまいましたが…

手作りのデザートもありすぐにSold Outしてしまいました~

どれもおいしくいただきました。Link松戸店さんありがとうございました。

食事もそこそこに、いよいよダーツ大会のスタートです!!

10チームに分かれ、4人1チームでのトーナメント制です。

ドリンク片手に誰かが投げるたびに会場が沸いていました~

投げる時はみんな真剣そのものです!!

意外と上手なメンバーもいれば、

日頃の運動不足か「肩があがらなくて投げられない」なんてメンバーも。

私もおそらく20年近くぶり?のダーツでしたので、

ギャラリーの視線で最初は緊張しましたが、終始楽しくプレイ出来ました。

ちゃんと的に当たって良かった( ;∀;)

優勝チームは「チームY」でした~おめでとうございます!

商品は、昨年同様「社長と行く高級焼肉」!

高級焼肉にありつけるのなら、来年に向けて密かに練習しようかな。笑

ダーツ大会が終了した後は、各々好きなことをして時間を過ごしました。

ビリヤードや卓球で盛り上がっているかと思えば、お酒片手に語り合っているメンバーも。

最後に全員で記念撮影をしました!

普段リモートワークのメンバーが多いジョーレンですが、

年に数回こんなイベントで顔を合わせる機会もあります。

2024年もジョーレンワンチームで頑張っていこ!と思った夜でした。

 

ジョーレン社員旅行2023

少しばかり時が過ぎてしまいましたが、2023年11月、沖縄へ社員旅行に行って参りました。

思い返せば
2018熱海と初島へ
2019
千葉県台風被害の復興支援で房総へ(その後コロナ渦)
行っておりますが、今回の社員旅行はなんと飛行機で『沖縄』へ。
会社の成長を感じますね!

もちろん参加の強制は一切なく、行きたい人たちだけですが

今回は17人のメンバーが集まりました!
お仕事に影響の無いよう土日の2日間です。

普段からリモートワークが多めですから、なかなか会えないメンバーもいるので
たっぷり交流するチャンス♩slackで行きたい場所を募り、皆で計画を立てました。

初日の朝、なかなかの寒さの中 羽田空港に集合!
沖縄は暖かい予報ですが、、??

沖縄の有名ファーストフード店 A&Wの本店で昼食です。
不思議な注文機材やアメリカンな店構えにテンションがあがります。
たくさん頼んで良いとのことでしたがハンバーガーひとつでもかなりのボリュームでした♪

みんなが頼んだ名物のルートビア。
サロンパスの味がする飲み物で非常に新鮮&衝撃的なお味でした。

観光組とダイビング組で別れて行動するはずでしたが、この日は風が強く、予定していたダイビングは中止になってしまいまったので、

急遽調べて「おきなワールド」へ。

みんなで鍾乳洞を見たり
ヘビを担いだり

ハブとマングースショーを見て満喫させていただきました。

社長が最前列で、一番楽しんでいたのが印象的でした(笑)

それを撮影する社員たち・・・(^^)

 

そして夜。

国際通りど真ん中のホテルに宿泊です。

カチャーシーなどのステージを見ることのできる居酒屋さんで

豪華なコースをいただきました。

ありがとうございます!!

恒例の 社長音頭のもとみんなで一本締めをして、楽しい1日を終えました・・・

と思いましたが、宴会の後も国際通りはまだまだ営業中で賑わっていたので、お土産を買ったり2次会に行ったり各々楽しみました!

(噂によると瀬長島までサウナに行った面々もいたとか?)

 

– – – – – – – – – – – – – –

 

2日目はグループに分かれての行動でした。

私のいたグループでは 

ちゅら海水族館 〜 古宇利島 〜 なかむらそば(沖縄そば有名店) 〜 ウミカジテラス 

を回りました。

ジンベエザメの大きさには圧倒されて、普段の案件の細々した悩みも吹っ飛びそうです。

そして初めは空を覆っていた雲も、徐々に晴れて青い沖縄の海を拝むことができました。

もう帰ったらお仕事頑張るしかありません。

 

副社長がドローンで空から撮影もしてくれて

素敵な思い出が残せました(^^)

 

最後のウミカジテラスは空港のそばなのでギリギリまで遊べるだろうと言うことで、

みんなで本当のギリギリまでグルメを堪能。

そして素敵な景色に非日常を感じて大いにストレス発散させていただきました。

来年は好調であればまた飛行機で北の方へ・・?と言うお話もあったので、

是非とも実現できるよう頑張っていきたいと思います。

【MySQL】ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction が出て困った話

先日MySQLで下記エラーに出会いました。

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

対応に手間取ったので、振り返ってみようと思います。

このエラーは何を表している?

データへ書き込むために待機していたトランザクションが、待機時間上限を超えた時に出るエラーのようです。
待機させられている理由は、前のトランザクションによって更新対象がロックされている為とのこと。

ちなみに待機時間の上限は innodb_lock_wait_timeout に定められており、デフォルトは50秒です。

どうやら行ロックを取得したトランザクションがcommitしないまま残ってしまったようで、
該当の行を更新しようとすると、50秒後に上記エラーが出るようになってしまいました。

wait_timeout(非対話型の接続)またはinteractive_timeout(対話型の接続)の時間を経過すると自動で切断されるようですが、
デフォルトは8時間と長いので急ぎ解消するためには、つかみっぱなしのトランザクションをkillする必要があります。

kill対象を判断するのに時間がかかってしまったので、その時調べたことをまとめておこうと思います。

killするスレッドを判断するために情報を集める

検証用にusersテーブルを作成します。MySqlのバージョンは 8.2.0でやっています。

CREATE TABLE users (
 id INT NOT NULL AUTO_INCREMENT ,
 name VARCHAR(255) NOT NULL ,
 email VARCHAR(255) NOT NULL ,
 PRIMARY KEY (id)
 );

INSERT INTO users(name, email)
VALUES ('TARO', 'taro@example.com'),
('JIRO', 'jiro@example.com'),
('SABURO', 'saburo@example.com');

mysql> select * from users;
 +----+--------+--------------------+
 | id | name | email |
 +----+--------+--------------------+
 | 1 | TARO | taro@example.com |
 | 2 | JIRO | jiro@example.com |
 | 3 | SABURO | saburo@example.com |
 +----+--------+--------------------+
 3 rows in set (0.00 sec)

この時点では、スレッドは1つです。

mysql>  show processlist;
 +----+------+-----------+------------+---------+------+-------+------------------+
 | Id | User | Host      | db         | Command | Time | State | Info             |
 +----+------+-----------+------------+---------+------+-------+------------------+
 | 13 | root | localhost | example_db | Query   |    0 | init  | show processlist |
 +----+------+-----------+------------+---------+------+-------+------------------+
 1 row in set, 1 warning (0.00 sec)

テーブルを作った上記のセッションを1つ目として、2つ目のセッションを新しく開始し、レコードの削除をしてcommitしないままクライアントを強制的に閉じます。
次に3つ目のセッションを新たに開始して1レコードのアップデートを行います。

-- 2つ目のセッションを新しく開始し、オートコミットをOFF
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)

-- レコードの削除
mysql> DELETE FROM users;
Query OK, 3 rows affected (0.00 sec)

-- commitしないまま切断

 

-- 3つ目のセッションを新しく開始し、オートコミットをOFF
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)

-- アップデートすると、50秒後に今回のエラーが発生する
 mysql> UPDATE users set name = 'ichiro' WHERE id = 1;
 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
 mysql> rollback;

再現できました。

ロックしているトランザクションを終了させないと、usersテーブルを更新出来ない状況になりました。
このまま8時間(デフォルト)経過すれば終了するのですが、8時間は長いので、どうにかkillしたいです。

show processlist

この時点でスレッドは3つ表示されます。

mysql> show processlist;
+----+------+-----------+------------+---------+------+-------+------------------+
| Id | User | Host      | db         | Command | Time | State | Info             |
+----+------+-----------+------------+---------+------+-------+------------------+
| 18 | root | localhost | example_db | Query   |    0 | init  | show processlist |
| 19 | root | localhost | example_db | Sleep   |  126 |       | NULL             |
| 20 | root | localhost | example_db | Sleep   |   53 |       | NULL             |
+----+------+-----------+------------+---------+------+-------+------------------+
3 rows in set, 1 warning (0.00 sec)

ちなみに、重いSQL等で処理が終わっていない場合は「Info」の部分にSQLステートメントが表示されますが、
今回はcommitまたはrallbackがされていないだけなので、Infoはnullになります。


SHOW ENGINE INNODB STATUS

killするならもう少し情報が欲しい、ということで、もう少し集めます。SHOW ENGINE INNODB STATUS で出てくる情報は多岐にわたりますが、トランザクションの情報も含まれています。

mysql> SHOW ENGINE INNODB STATUS;
-- (省略) --

------------
TRANSACTIONS
------------
Trx id counter 1871
Purge done for trx's n:o < 1870 undo n:o < 0 state: running but idle
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421357384966952, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421357384965336, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421357384964528, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 421357384963720, not started
0 lock struct(s), heap size 1128, 0 row lock(s)
---TRANSACTION 1865, ACTIVE 771 sec
2 lock struct(s), heap size 1128, 4 row lock(s), undo log entries 3
MySQL thread id 19, OS thread handle 139881921705728, query id 218 localhost root

-- (省略) --

「TRANSACTIONS」の部分を確認します。

「undo log entries 3」の部分がコミットもロールバックもしていない行数です。

「MySQL thread id 19」がSHOW PROCESSLISTのIdと一致します。Id:19が長時間(771秒)経過しているという情報が取れました。加えて「3行変更しているのにコミットされていない」という情報を取得出来ました。

select * from information_schema.INNODB_TRX

information_schema.INNODB_TRX から現在実行されているすべてのトランザクションに関する情報を取得出来ます。

mysql> select * from information_schema.INNODB_TRX order by trx_id\G
*************************** 1. row ***************************
                    trx_id: 1865
                 trx_state: RUNNING
               trx_started: 2023-11-29 10:17:20
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 5
       trx_mysql_thread_id: 19
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 1
          trx_lock_structs: 2
     trx_lock_memory_bytes: 1128
           trx_rows_locked: 4
         trx_rows_modified: 3
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
       trx_schedule_weight: NULL
*************************** 2. row ***************************
                    trx_id: 421357384966952
                 trx_state: RUNNING
               trx_started: 2023-11-29 10:30:55
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
       trx_mysql_thread_id: 20
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 0
          trx_lock_structs: 0
     trx_lock_memory_bytes: 1128
           trx_rows_locked: 0
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
       trx_schedule_weight: NULL
2 rows in set (0.00 sec)

2行出力されましたが、trx_started (トランザクション開始時間)が早い方が長い間完了していないことになります。

より長時間経過している1行目を見ると、trx_rows_modified(このトランザクションで変更および挿入された行の数)が3で、ここでも3行更新しているが完了していないことが読み取れます。また、trx_mysql_thread_id(MySQL スレッド ID)はSHOW PROCESSLISTのIdと一致するので、これはshow processlistのId:19を指しています。

過去発行したクエリも確認する

先ほどから目を付けているID:19が発行したクエリを見てみます。

まずはMySQL スレッド IDとperformance_schema.threadsからTHREAD_IDを取得します。

mysql> SELECT thread_id FROM performance_schema.threads WHERE processlist_id = 19;
+-----------+
| thread_id |
+-----------+
|        56 |
+-----------+
1 row in set (0.00 sec)

そのTHREAD_IDとperformance_schema.events_statements_historyから、過去のクエリが少し分かります。killして大丈夫かの判断材料になりそうです。

ただし、performance_schema.events_statements_history は最新10件(デフォルト)しか保持していないので、それより前の情報はここからは取得出来ません。

mysql> SELECT THREAD_ID,SQL_TEXT FROM performance_schema.events_statements_history WHERE thread_id = 56 ORDER BY TIMER_START;
+-----------+----------------------------------+
| THREAD_ID | SQL_TEXT                         |
+-----------+----------------------------------+
|        56 | select @@version_comment limit 1 |
|        56 | select $$                        |
|        56 | SELECT DATABASE()                |
|        56 | NULL                             |
|        56 | show databases                   |
|        56 | show tables                      |
|        56 | NULL                             |
|        56 | select * from users              |
|        56 | set autocommit = 0               |
|        56 | DELETE FROM users                |
+-----------+----------------------------------+
10 rows in set (0.00 sec)

情報を集めたので、最後にID:19をkillしてアップデートできるようになることを確認します。

mysql> kill 19;
Query OK, 0 rows affected (0.00 sec)

mysql> show processlist;
+----+------+-----------+------------+---------+------+-------+------------------+
| Id | User | Host      | db         | Command | Time | State | Info             |
+----+------+-----------+------------+---------+------+-------+------------------+
| 18 | root | localhost | example_db | Query   |    0 | init  | show processlist |
| 20 | root | localhost | example_db | Sleep   | 1911 |       | NULL             |
+----+------+-----------+------------+---------+------+-------+------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> UPDATE users set name = 'ichiro' WHERE id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

-- id1のnameがTAROからichiroに更新されている
mysql> select * from users;
+----+--------+--------------------+
| id | name   | email              |
+----+--------+--------------------+
|  1 | ichiro | taro@example.com   |
|  2 | JIRO   | jiro@example.com   |
|  3 | SABURO | saburo@example.com |
+----+--------+--------------------+
3 rows in set (0.00 sec)

usersテーブルをアップデート出来ました。

 

 

 

今回は【MySQL】ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transactionが発生しkillしないと解決しない場合の調査方法についてまとめてみました。まだまだ知らないことが多いので、今後もMySQLを学んでいきたいと思います。

Continue reading “【MySQL】ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction が出て困った話”

【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系を触る場合は初期構築時に導入することをお勧めします!!