Improve dashboard performance with database-level aggregation#6570
Improve dashboard performance with database-level aggregation#6570nobuhiko wants to merge 4 commits intoEC-CUBE:4.3from
Conversation
0a62f8c to
59ad6e7
Compare
This commit addresses Issue EC-CUBE#6558 by optimizing the sales data calculation on the admin dashboard. Previously, the system loaded all Order entities into memory and performed calculations in PHP, which caused performance issues with large datasets. Changes: - Added DateFormat DQL function for cross-database date formatting - Implemented getSalesDataGroupedByDate() in OrderRepository for database-level aggregation using GROUP BY, SUM, and COUNT - Updated AdminController to use the new repository method - Fixed parameter binding issues (removed colons from parameter names) - Fixed date boundary conditions in getSalesByDay() and getSalesByMonth() - Added performance tests to verify efficiency with large datasets - Added dashboard screenshot Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
59ad6e7 to
f9fd37b
Compare
- Fix deleteAllRows to include dependent tables (dtb_mail_history, dtb_order_item, dtb_shipping) before dtb_order to avoid FK constraint violations on PostgreSQL - Reduce order count from 100 to 20 in performance test to prevent CI timeout (createOrder is expensive) - Remove unstable performance timing/memory assertions that are environment-dependent - Remove debug comments from AdminController - Remove accidentally committed screenshots Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthrough管理ダッシュボードの売上集計をPHP側の逐次集計からデータベース集計へ移行し、DATEFORMAT DQL関数を追加。OrderRepositoryに集計取得メソッドを実装し、AdminControllerとテストをそれに合わせて修正しています。 変更内容
Sequence Diagram(s)sequenceDiagram
participant AdminController as AdminController
participant OrderRepo as OrderRepository
participant Doctrine as Doctrine/DB
AdminController->>OrderRepo: getSalesDataGroupedByDate(fromDate,toDate,excludes,format)
OrderRepo->>Doctrine: DQL using DATEFORMAT(...) with params
Doctrine->>DB: SQL (driver-specific DATE_FORMAT/TO_CHAR/STRFTIME)
DB-->>Doctrine: aggregated rows (date_key,total_price,order_count)
Doctrine-->>OrderRepo: raw results
OrderRepo-->>AdminController: results array
AdminController->>AdminController: convert results into chart dataset
AdminController-->>Client: render dashboard with aggregated data
推定コード審査時間🎯 3 (中程度) | ⏱️ ~20分 ポエム
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
src/Eccube/Repository/OrderRepository.php (1)
486-504: パラメータと戻り値の型宣言を追加してくださいコーディングガイドラインに従い、
$fromDateと$toDateのパラメータ型宣言、および戻り値の型宣言を追加することを推奨します。♻️ 型宣言の追加案
- public function getSalesDataGroupedByDate($fromDate, $toDate, array $excludeStatuses, $format = 'Y/m/d') + public function getSalesDataGroupedByDate(\DateTime $fromDate, \DateTime $toDate, array $excludeStatuses, string $format = 'Y/m/d'): array {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Eccube/Repository/OrderRepository.php` around lines 486 - 504, 関数 getSalesDataGroupedByDate のシグネチャに型宣言を追加してください:$fromDate と $toDate は DateTimeInterface(\DateTimeInterface)を使い、$format は string、$excludeStatuses は既に array なのでそのままにしてメソッドの戻り値に array を追加します(例: public function getSalesDataGroupedByDate(\DateTimeInterface $fromDate, \DateTimeInterface $toDate, array $excludeStatuses, string $format = 'Y/m/d'): array)。これにより setParameter に渡す日付オブジェクトの型が明確になり、戻り値が配列であることが明示されます。src/Eccube/Doctrine/ORM/Query/DateFormat.php (1)
57-70: コーディングガイドラインに従い、型宣言を追加することを推奨しますPSR-12およびプロジェクトのコーディングガイドラインに従い、パラメータと戻り値の型宣言を追加することを検討してください。
♻️ 型宣言の追加案
- public function parse(Parser $parser) + public function parse(Parser $parser): void { $parser->match(Lexer::T_IDENTIFIER);- public function getSql(SqlWalker $sqlWalker) + public function getSql(SqlWalker $sqlWalker): string {- protected function convertFormat($phpFormat, $driver) + protected function convertFormat(string $phpFormat, string $driver): string {Also applies to: 72-101
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Eccube/Doctrine/ORM/Query/DateFormat.php` around lines 57 - 70, このparseメソッドと同じファイル内の関連メソッドにPSR-12準拠の型宣言を追加してください:メソッド宣言をparse(Parser $parser): voidのようにパラメータ型と戻り値型を付与し、クラス内で使われているプロパティ($date, $formatなど)が保持する型が明らかならプロパティ宣言に型を追加してください(例: private Node|Expr $date; private Node|Expr $format; など)。対象となる固有シンボルはメソッド parse(Parser $parser), プロパティ $date, $format およびファイル内の同様のメソッド(該当箇所の parse と同様に型が欠けているメソッド)です。src/Eccube/Controller/Admin/AdminController.php (1)
617-631: 月次集計時のイテレーション効率について(オプション)
'Y/m'フォーマット使用時(年間売上、Line 311)、forループは日単位でイテレートするため、約365回のループで最大12個のユニークキーのみ生成されます。機能的には正しく動作しますが、月次集計の場合は月単位でイテレートする方が効率的です。現状でも正しく動作するため、優先度は低いです。
♻️ 月次フォーマット時の効率化案(参考)
// 例: フォーマットに応じてインターバルを変更 $interval = str_contains($format, '/d') ? 'addDay' : 'addMonth'; for ($date = $fromDate->copy(); $date <= $toDate; $date = $date->$interval()) { // ... }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Eccube/Controller/Admin/AdminController.php` around lines 617 - 631, The convert function currently always iterates day-by-day between $fromDate and $toDate which is inefficient for monthly aggregations; modify the date iteration in convert to choose the step based on the $format (e.g., use addMonth() when format indicates monthly like 'Y/m' and addDay() for daily), so the for loop that builds $raw advances by the appropriate interval and still initializes $raw[$date->format($format)]['price'] and ['count'] for each period before merging $results.tests/Eccube/Tests/Web/Admin/IndexControllerTest.php (1)
266-273:DebugStackとSQLLoggerはDoctrine DBAL 3.xで非推奨です
Doctrine\DBAL\Logging\DebugStackとSQLLoggerインターフェースはDoctrine DBAL 3.xで非推奨となり、Middleware + PSR-3ロガーの方式に置き換えられています。現在のコード(DBAL 3.3使用中)では機能していますが、今後のDBALアップグレード時に動作しなくなる可能性があります。テスト目的では現在の実装で問題ありませんが、将来的なリファクタリング候補として認識しておいてください。推奨される置き換え方は
Doctrine\DBAL\Logging\MiddlewareとPSR-3ロガーの組み合わせです。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Eccube/Tests/Web/Admin/IndexControllerTest.php` around lines 266 - 273, テスト内で使用している非推奨の DebugStack / SQLLogger を将来の DBAL 3→4 対応のために置き換えてください: 現在の entityManager->getConnection() と connection->getConfiguration()->setSQLLogger($logger) で DebugStack を使っている箇所を、Doctrine\DBAL\Logging\Middleware を用いる実装に差し替え、PSR-3 互換ロガー(例: a test-local logger 実装)をミドルウェアに渡してクエリを収集するように変更してください(参照シンボル: DebugStack, SQLLogger, entityManager->getConnection(), connection->getConfiguration()->setSQLLogger(), Doctrine\DBAL\Logging\Middleware, PSR-3 logger)。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Eccube/Doctrine/ORM/Query/DateFormat.php`:
- Around line 72-101: Add explicit type declarations: change getSql(SqlWalker
$sqlWalker) to getSql(SqlWalker $sqlWalker): string, change parse() to parse():
void, and add argument and return types to convertFormat(string $phpFormat,
string $platform): string; update any signatures and usages in this class
accordingly. Replace the use of
$sqlWalker->getConnection()->getDriver()->getDatabasePlatform()->getName() with
platform instance checks (e.g. instanceof
\Doctrine\DBAL\Platforms\SqlitePlatform, PostgreSQLPlatform, MySQLPlatform)
inside getSql (and apply the same pattern in Extract.php and Normalize.php) so
the switch over $driver is implemented via instanceof checks to avoid the
deprecated AbstractPlatform::getName() API. Ensure the converted format logic
still calls convertFormat with the platform identifier or use separate branches
per platform instance.
In `@src/Eccube/Repository/OrderRepository.php`:
- Around line 488-489: The code interpolates $format directly into the DQL in
OrderRepository when building the query
($this->createQueryBuilder('o')->select("DATEFORMAT(o.order_date, '{$format}')
as date_key")), which risks DQL injection; add a whitelist check inside the
repository method (or a private helper) that allows only specific safe format
strings (e.g., a predefined array or switch of allowed tokens), fallback or
throw an InvalidArgumentException for invalid values, and only interpolate the
validated/normalized format into the DQL (or map formats to safe DQL fragments)
so untrusted input can never be injected into the DATEFORMAT expression.
---
Nitpick comments:
In `@src/Eccube/Controller/Admin/AdminController.php`:
- Around line 617-631: The convert function currently always iterates day-by-day
between $fromDate and $toDate which is inefficient for monthly aggregations;
modify the date iteration in convert to choose the step based on the $format
(e.g., use addMonth() when format indicates monthly like 'Y/m' and addDay() for
daily), so the for loop that builds $raw advances by the appropriate interval
and still initializes $raw[$date->format($format)]['price'] and ['count'] for
each period before merging $results.
In `@src/Eccube/Doctrine/ORM/Query/DateFormat.php`:
- Around line 57-70:
このparseメソッドと同じファイル内の関連メソッドにPSR-12準拠の型宣言を追加してください:メソッド宣言をparse(Parser $parser):
voidのようにパラメータ型と戻り値型を付与し、クラス内で使われているプロパティ($date,
$formatなど)が保持する型が明らかならプロパティ宣言に型を追加してください(例: private Node|Expr $date; private
Node|Expr $format; など)。対象となる固有シンボルはメソッド parse(Parser $parser), プロパティ $date,
$format およびファイル内の同様のメソッド(該当箇所の parse と同様に型が欠けているメソッド)です。
In `@src/Eccube/Repository/OrderRepository.php`:
- Around line 486-504: 関数 getSalesDataGroupedByDate
のシグネチャに型宣言を追加してください:$fromDate と $toDate は
DateTimeInterface(\DateTimeInterface)を使い、$format は string、$excludeStatuses は既に
array なのでそのままにしてメソッドの戻り値に array を追加します(例: public function
getSalesDataGroupedByDate(\DateTimeInterface $fromDate, \DateTimeInterface
$toDate, array $excludeStatuses, string $format = 'Y/m/d'): array)。これにより
setParameter に渡す日付オブジェクトの型が明確になり、戻り値が配列であることが明示されます。
In `@tests/Eccube/Tests/Web/Admin/IndexControllerTest.php`:
- Around line 266-273: テスト内で使用している非推奨の DebugStack / SQLLogger を将来の DBAL 3→4
対応のために置き換えてください: 現在の entityManager->getConnection() と
connection->getConfiguration()->setSQLLogger($logger) で DebugStack
を使っている箇所を、Doctrine\DBAL\Logging\Middleware を用いる実装に差し替え、PSR-3 互換ロガー(例: a
test-local logger 実装)をミドルウェアに渡してクエリを収集するように変更してください(参照シンボル: DebugStack,
SQLLogger, entityManager->getConnection(),
connection->getConfiguration()->setSQLLogger(),
Doctrine\DBAL\Logging\Middleware, PSR-3 logger)。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c2babfb2-2f39-48ca-94f5-dc2296ae25b2
📒 Files selected for processing (5)
app/config/eccube/packages/doctrine.yamlsrc/Eccube/Controller/Admin/AdminController.phpsrc/Eccube/Doctrine/ORM/Query/DateFormat.phpsrc/Eccube/Repository/OrderRepository.phptests/Eccube/Tests/Web/Admin/IndexControllerTest.php
| public function getSql(SqlWalker $sqlWalker) | ||
| { | ||
| $driver = $sqlWalker->getConnection()->getDriver()->getDatabasePlatform()->getName(); | ||
| $dateField = $this->date->dispatch($sqlWalker); | ||
| $formatValue = $this->format->dispatch($sqlWalker); | ||
|
|
||
| // フォーマット文字列から引用符を除去 | ||
| $phpFormat = trim($formatValue, "'\""); | ||
|
|
||
| // データベースプラットフォームに応じたフォーマットに変換 | ||
| switch ($driver) { | ||
| case 'sqlite': | ||
| $dbFormat = $this->convertFormat($phpFormat, 'sqlite'); | ||
| // SQLiteの場合、DATETIME()でカラム値を正規化してからSTRFTIME()を適用 | ||
| // これにより、タイムゾーン付き日時文字列が正しく処理される | ||
| $sql = sprintf("STRFTIME('%s', DATETIME(%s))", $dbFormat, $dateField); | ||
| break; | ||
| case 'postgresql': | ||
| $dbFormat = $this->convertFormat($phpFormat, 'postgresql'); | ||
| $sql = sprintf("TO_CHAR(%s, '%s')", $dateField, $dbFormat); | ||
| break; | ||
| case 'mysql': | ||
| default: | ||
| $dbFormat = $this->convertFormat($phpFormat, 'mysql'); | ||
| $sql = sprintf("DATE_FORMAT(%s, '%s')", $dateField, $dbFormat); | ||
| break; | ||
| } | ||
|
|
||
| return $sql; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Doctrine DBAL 3.x AbstractPlatform getName deprecated
💡 Result:
Doctrine\DBAL\Platforms\AbstractPlatform::getName() was deprecated in DBAL 3.2 because relying on a string “platform name” is discouraged; identify the platform by its class (or instanceof) instead. [1] This deprecation was motivated by the idea that instanceof / FQCN checks are clearer and less fragile than string names. [3]
In DBAL 4, AbstractPlatform::getName() was removed. [2]
Replace code like:
$platform = $connection->getDatabasePlatform();
if ($platform->getName() === 'postgresql') { ... }With:
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
$platform = $connection->getDatabasePlatform();
if ($platform instanceof PostgreSQLPlatform) { ... }
// or: match ($platform::class) { ... }Sources: [1] [2] [3]
🏁 Script executed:
cat -n src/Eccube/Doctrine/ORM/Query/DateFormat.php | sed -n '1,110p'Repository: EC-CUBE/ec-cube
Length of output: 3859
🏁 Script executed:
cat -n src/Eccube/Doctrine/ORM/Query/DateFormat.php | sed -n '103,140p'Repository: EC-CUBE/ec-cube
Length of output: 600
🏁 Script executed:
# Check parent class FunctionNode from Doctrine ORM to understand method signatures
rg "class FunctionNode" --type php -A 20Repository: EC-CUBE/ec-cube
Length of output: 41
🏁 Script executed:
# Find other custom Doctrine function classes in the codebase
rg "extends FunctionNode" --type php -A 30Repository: EC-CUBE/ec-cube
Length of output: 6887
🏁 Script executed:
# Quick check to see if Extract.php getSql method has return type
rg "class Extract" -A 100 src/Eccube/Doctrine/ORM/Query/Extract.php | grep -A 5 "public function getSql"Repository: EC-CUBE/ec-cube
Length of output: 295
型宣言を追加し、非推奨APIをリファクタリングしてください
-
型宣言の追加: PSR-12ガイドラインに従い、
parse()とgetSql()にはメソッドの戻り値の型宣言voidおよびstringをそれぞれ追加してください。convertFormat()にも引数と戻り値の型宣言stringを追加してください。 -
非推奨API:
AbstractPlatform::getName()は Doctrine DBAL 3.2 で非推奨となり、DBAL 4 では削除されています。既存コード(Extract.php、Normalize.php)も同じパターンを使用していますが、将来的なリファクタリングとして、プラットフォームクラスのインスタンスチェック(instanceof)での置き換えを検討してください。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Eccube/Doctrine/ORM/Query/DateFormat.php` around lines 72 - 101, Add
explicit type declarations: change getSql(SqlWalker $sqlWalker) to
getSql(SqlWalker $sqlWalker): string, change parse() to parse(): void, and add
argument and return types to convertFormat(string $phpFormat, string $platform):
string; update any signatures and usages in this class accordingly. Replace the
use of
$sqlWalker->getConnection()->getDriver()->getDatabasePlatform()->getName() with
platform instance checks (e.g. instanceof
\Doctrine\DBAL\Platforms\SqlitePlatform, PostgreSQLPlatform, MySQLPlatform)
inside getSql (and apply the same pattern in Extract.php and Normalize.php) so
the switch over $driver is implemented via instanceof checks to avoid the
deprecated AbstractPlatform::getName() API. Ensure the converted format logic
still calls convertFormat with the platform identifier or use separate branches
per platform instance.
| $qb = $this->createQueryBuilder('o') | ||
| ->select("DATEFORMAT(o.order_date, '{$format}') as date_key") |
There was a problem hiding this comment.
$formatのDQL文字列への直接補間に注意が必要です
$formatパラメータがDQL文字列に直接補間されています。現在の呼び出し元(AdminController)ではハードコードされた値のみ使用されていますが、将来的にユーザー入力が渡される可能性がある場合、DQLインジェクションのリスクがあります。
許可されたフォーマットのホワイトリスト検証を追加することを検討してください。
🛡️ フォーマット検証の追加案
public function getSalesDataGroupedByDate($fromDate, $toDate, array $excludeStatuses, $format = 'Y/m/d')
{
+ $allowedFormats = ['Y/m/d', 'Y/m', 'Y-m-d', 'Y-m'];
+ if (!in_array($format, $allowedFormats, true)) {
+ throw new \InvalidArgumentException('Invalid date format');
+ }
+
$qb = $this->createQueryBuilder('o')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/Eccube/Repository/OrderRepository.php` around lines 488 - 489, The code
interpolates $format directly into the DQL in OrderRepository when building the
query ($this->createQueryBuilder('o')->select("DATEFORMAT(o.order_date,
'{$format}') as date_key")), which risks DQL injection; add a whitelist check
inside the repository method (or a private helper) that allows only specific
safe format strings (e.g., a predefined array or switch of allowed tokens),
fallback or throw an InvalidArgumentException for invalid values, and only
interpolate the validated/normalized format into the DQL (or map formats to safe
DQL fragments) so untrusted input can never be injected into the DATEFORMAT
expression.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/Eccube/Controller/Admin/AdminController.php (2)
614-621: 型宣言の追加を検討してください。コーディングガイドラインに従い、
$resultsと$formatパラメータに型宣言を追加することを推奨します。♻️ 型宣言の追加
- protected function convert($results, Carbon $fromDate, Carbon $toDate, $format) + protected function convert(array $results, Carbon $fromDate, Carbon $toDate, string $format): arrayAs per coding guidelines: "Use PHP type declarations for parameters and return types"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Eccube/Controller/Admin/AdminController.php` around lines 614 - 621, The convert method lacks parameter and return type declarations; update the signature of protected function convert to include types (e.g., array $results, Carbon $fromDate, Carbon $toDate, string $format) and add an appropriate return type (likely : array) to match the method's behavior, then adjust any callers if needed to satisfy the new signatures; reference the method name convert and the parameters $results and $format when making these changes.
618-621: 'Y/m'フォーマット時のループ効率について。年間チャート(
'Y/m'フォーマット)の場合、このループは約365回反復して約12個のユニークキーを作成します。現時点では機能的に正しいですが、将来的にパフォーマンスが懸念される場合は、フォーマットに応じて日単位・月単位でインクリメントする方法を検討できます。♻️ フォーマット別のインクリメント例
$raw = []; - for ($date = $fromDate->copy(); $date <= $toDate; $date = $date->addDay()) { + $interval = ($format === 'Y/m') ? 'addMonth' : 'addDay'; + for ($date = $fromDate->copy(); $date <= $toDate; $date = $date->$interval()) { $raw[$date->format($format)]['price'] = 0; $raw[$date->format($format)]['count'] = 0; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Eccube/Controller/Admin/AdminController.php` around lines 618 - 621, ループが常に日単位($date = $date->addDay())で回っており、format が 'Y/m' のときは365回回す無駄があるため、AdminController の該当ループで $format をチェックして月単位でインクリメントするよう修正してください(参照するシンボル: $fromDate, $toDate, $format, $raw, $date)。具体的には if ($format === 'Y/m') に分岐して $date を月の初日に揃えた上で月増分(Carbon の addMonth()/addMonthNoOverflow() 等)を使い、そうでなければ既存の addDay() を使うようにしてユニークキー生成と初期化処理($raw[$date->format($format)]['price']=0; $raw[$date->format($format)]['count']=0;)は共通化してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/Eccube/Controller/Admin/AdminController.php`:
- Around line 614-621: The convert method lacks parameter and return type
declarations; update the signature of protected function convert to include
types (e.g., array $results, Carbon $fromDate, Carbon $toDate, string $format)
and add an appropriate return type (likely : array) to match the method's
behavior, then adjust any callers if needed to satisfy the new signatures;
reference the method name convert and the parameters $results and $format when
making these changes.
- Around line 618-621: ループが常に日単位($date = $date->addDay())で回っており、format が 'Y/m'
のときは365回回す無駄があるため、AdminController の該当ループで $format
をチェックして月単位でインクリメントするよう修正してください(参照するシンボル: $fromDate, $toDate, $format, $raw,
$date)。具体的には if ($format === 'Y/m') に分岐して $date を月の初日に揃えた上で月増分(Carbon の
addMonth()/addMonthNoOverflow() 等)を使い、そうでなければ既存の addDay()
を使うようにしてユニークキー生成と初期化処理($raw[$date->format($format)]['price']=0;
$raw[$date->format($format)]['count']=0;)は共通化してください。
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 273e3dd8-8d97-4d5b-8c83-5facb592847f
📒 Files selected for processing (1)
src/Eccube/Controller/Admin/AdminController.php
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 4.3 #6570 +/- ##
============================================
- Coverage 82.67% 79.25% -3.43%
- Complexity 0 6668 +6668
============================================
Files 480 477 -3
Lines 26568 26724 +156
============================================
- Hits 21966 21180 -786
- Misses 4602 5544 +942
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@nobuhiko |
概要
管理画面ホームの売上データ表示において、大量の注文データがある場合にパフォーマンスが低下する問題を解決しました。Issue #6558 で報告された通り、従来はPHP側で全注文データを読み込んで集計していましたが、データベース側で集計することで大幅なパフォーマンス改善を実現しました。
問題
従来の実装では、売上データを表示する際に以下の処理を行っていました:
Orderエンティティ全件をPHPメモリに読み込みこの方式では、注文数が増えるほど以下の問題が発生:
解決策
データベース側で集計を行うように変更:
GROUP BY、SUM()、COUNT()を使用したデータベースレベルの集計変更内容
1. DateFormat DQL関数の実装 (
src/Eccube/Doctrine/ORM/Query/DateFormat.php)クロスデータベース対応の日付フォーマット関数を追加:
DATE_FORMAT()TO_CHAR()STRFTIME()2. OrderRepository の拡張 (
src/Eccube/Repository/OrderRepository.php)データベース側で日付ごとに売上を集計する新メソッドを追加。
3. AdminController の最適化 (
src/Eccube/Controller/Admin/AdminController.php)getData()メソッドを修正し、新しいリポジトリメソッドを使用:excludes→excludes)<=→<)4. パフォーマンステストの追加 (
tests/Eccube/Tests/Web/Admin/IndexControllerTest.php)パフォーマンス検証結果
テスト環境
結果
技術的考慮事項
セキュリティ
後方互換性
データベース互換性
スクリーンショット
管理画面ホーム(34,807件の注文データで高速表示)
テスト
実行したテスト
テスト結果
関連Issue
Fixes #6558
チェックリスト
🤖 Generated with Claude Code
Summary by CodeRabbit
リリースノート
新機能
テスト