From 583a60b5261223d5a2cff478ebf96a13480b8f0b Mon Sep 17 00:00:00 2001 From: eliseekn Date: Wed, 23 Aug 2023 22:48:37 +0000 Subject: [PATCH] Remove SQLite support Replace days and months names translation by built-in Carbon locale method --- README.md | 50 ++++++---- src/LaravelMetrics.php | 204 ++++++++++++++++++++++++----------------- 2 files changed, 152 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index c81cda0..bab72f8 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,27 @@ composer require eliseekn/laravel-metrics ``` ## Features -- MySQL and SQLite support +- MySQL support - Verbose query builder - Custom date column definition -- No ambiguous column ## Usage Import the `Eliseekn\LaravelMetrics\LaravelMetrics` class in your controller and use it as follows : ```php -// generate trends of the sum of the orders amount for the last 12 months of the current year +// generate trends of the sum of the orders amount for the current year LaravelMetrics::query(Order::query()) ->sum('amount') - ->byMonth(12) + ->byMonth() ->trends(); -// generate trends of count of the products for the last 3 years +// generate trends of the sum of the orders amount for the last 6 months of the current year including the current month +LaravelMetrics::query(Order::query()) + ->sum('amount') + ->byMonth(6) + ->trends(); + +// generate trends of count of the products for the last 3 years including the current year LaravelMetrics::query(Product::query()) ->count() ->byYear(3) @@ -38,47 +43,52 @@ LaravelMetrics::query(Order::query()) ->byYear() ->metrics(); -// generate total count of the product for the current day of the current month +// generate total count of the product for the current day of the current week LaravelMetrics::query(Product::query()) ->count() ->byDay(1) ->metrics(); -// generate trends of count of posts for the last 7 days of the current month +// generate total count of the product for the current month +LaravelMetrics::query(Product::query()) + ->count() + ->byWeek() + ->metrics(); + +// generate trends of count of posts for the current week // by using a custom query and a specific date column (published_at) LaravelMetrics::query( Post::query()->where('user_id', auth()->id()) ) ->count() - ->byDay(7) + ->byDay() ->dateColumn('published_at') ->trends(); // generate trends of count of posts for a range of dates -// by using a custom query and a specific date column (published_at) LaravelMetrics::query( Post::query()->where('user_id', auth()->id()) ) ->count() ->between('2020-05-01', '2022-08-21') - ->dateColumn('published_at') ->trends(); ``` ### Types of periods ```php -->byDay($count = 0) -->byMonth($count = 0) -->byYear($count = 0) -->by($period, $count = 0) +->byDay(int $count = 0) +->byWeek(int $count = 0) +->byMonth(int $count = 0) +->byYear(int $count = 0) +->by(string $period, $count = 0) ->between($startDate, $endDate) ``` ```php -$count = 0 => for every day, month or year -$count = 1 => for the current day, month or year -$count > 1 => for an interval of day, month or year from the $count value to now -$period = 'day', 'month' or 'year' +$count = 0 => for every day, week, month or year +$count = 1 => for the current day, week, month or year +$count > 1 => for an interval of day, week, month or year from the $count value to now +$period = 'day', 'week', 'month' or 'year' ``` ### Types of aggregates @@ -96,6 +106,10 @@ $period = 'day', 'month' or 'year' ->metrics() // retrieves total values ``` +### Translations + +Days and months names are automatically translated using `config(app.locale)` except 'Week'. + ## Changelog Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. diff --git a/src/LaravelMetrics.php b/src/LaravelMetrics.php index 23ad99a..e37be50 100644 --- a/src/LaravelMetrics.php +++ b/src/LaravelMetrics.php @@ -15,44 +15,20 @@ /** * LaravelMetrics - * - * Generate metrics and trends data from your database + * + * Generate easily metrics and trends data of your models for your dashboards. */ class LaravelMetrics { protected const DAY = 'day'; + protected const WEEK = 'week'; protected const MONTH = 'month'; protected const YEAR = 'year'; - protected const COUNT = 'COUNT'; - protected const AVERAGE = 'AVG'; - protected const SUM = 'SUM'; - protected const MAX = 'MAX'; - protected const MIN = 'MIN'; - - protected const DAY_NAME = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday' - ]; - - protected const MONTH_NAME = [ - 'December', - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - ]; + protected const COUNT = 'count'; + protected const AVERAGE = 'avg'; + protected const SUM = 'sum'; + protected const MAX = 'max'; + protected const MIN = 'min'; protected string $table; protected string $column = 'id'; @@ -63,6 +39,7 @@ class LaravelMetrics protected int $year; protected int $month; protected int $day; + protected int $week; public function __construct(protected Builder $builder) { @@ -71,6 +48,7 @@ public function __construct(protected Builder $builder) $this->year = Carbon::now()->year; $this->month = Carbon::now()->month; $this->day = Carbon::now()->day; + $this->week = Carbon::now()->week; } public static function query(Builder $builder): self @@ -78,38 +56,37 @@ public static function query(Builder $builder): self return new self($builder); } - public function byDay(int $count = 0): self + public function by(string $period, int $count = 0): self { - $this->period = self::DAY; + $period = strtolower($period); + + if (!in_array($period, [self::DAY, self::MONTH, self::YEAR])) { + throw new InvalidPeriodException(); + } + + $this->period = $period; $this->count = $count; return $this; } - public function byMonth(int $count = 0): self + public function byDay(int $count = 0): self { - $this->period = self::MONTH; - $this->count = $count; - return $this; + return $this->by(self::DAY, $count); } - public function byYear(int $count = 0): self + public function byWeek(int $count = 0): self { - $this->period = self::YEAR; - $this->count = $count; - return $this; + return $this->by(self::WEEK, $count); } - public function by(string $period, int $count = 0): self + public function byMonth(int $count = 0): self { - $period = strtolower($period); - - if (!in_array($period, [self::DAY, self::MONTH, self::YEAR])) { - throw new InvalidPeriodException(); - } + return $this->by(self::MONTH, $count); + } - $this->period = $period; - $this->count = $count; - return $this; + public function byYear(int $count = 0): self + { + return $this->by(self::YEAR, $count); } public function between(string $start, string $end): self @@ -176,24 +153,42 @@ protected function metricsData(): mixed ->selectRaw("$this->type($this->column) as data") ->whereYear($this->dateColumn, $this->year) ->whereMonth($this->dateColumn, $this->month) - ->when($this->count > 0, function (QueryBuilder $query) { - $column = self::driver() === 'sqlite' - ? "strftime('%d', $this->dateColumn)" - : "day($this->dateColumn)"; - - return $query->whereBetween(DB::raw($column), [ + ->where(DB::raw($this->formatPeriod(self::WEEK)), $this->week) + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw("day($this->dateColumn)"), $this->day); + }) + ->when($this->count > 1, function (QueryBuilder $query) { + return $query->whereBetween(DB::raw("day($this->dateColumn)"), [ Carbon::now()->subDays($this->count)->day, $this->day ]); }) ->first(), + self::WEEK => $this->builder + ->toBase() + ->selectRaw("$this->type($this->column) as data") + ->whereYear($this->dateColumn, $this->year) + ->whereMonth($this->dateColumn, $this->month) + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw($this->formatPeriod(self::WEEK)), $this->week); + }) + ->when($this->count > 1, function (QueryBuilder $query) { + return $query->whereBetween(DB::raw($this->formatPeriod(self::WEEK)), [ + Carbon::now()->subWeeks($this->count)->week, $this->week + ]); + }) + ->first(), + self::MONTH => $this->builder ->toBase() ->selectRaw("$this->type($this->column) as data") ->whereYear($this->dateColumn, $this->year) - ->when($this->count > 0, function (QueryBuilder $query) { + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw($this->formatPeriod(self::MONTH)), $this->month); + }) + ->when($this->count > 1, function (QueryBuilder $query) { return $query->whereBetween(DB::raw($this->formatPeriod(self::MONTH)), [ - Carbon::now()->subMonths($this->count)->month, $this->month + $this->parseMonth(), $this->month ]); }) ->first(), @@ -201,7 +196,10 @@ protected function metricsData(): mixed self::YEAR => $this->builder ->toBase() ->selectRaw("$this->type($this->column) as data") - ->when($this->count > 0, function (QueryBuilder $query) { + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw($this->formatPeriod(self::YEAR)), $this->year); + }) + ->when($this->count > 1, function (QueryBuilder $query) { return $query->whereBetween(DB::raw($this->formatPeriod(self::YEAR)), [ Carbon::now()->subYears($this->count)->year, $this->year ]); @@ -218,8 +216,9 @@ protected function trendsData(): Collection return $this->builder ->toBase() ->selectRaw("$this->type($this->column) as data, date($this->dateColumn) as label") - ->whereBetween(DB::raw('date(' . $this->dateColumn . ')'), [$this->period[0], $this->period[1]]) + ->whereBetween(DB::raw("date($this->dateColumn)"), [$this->period[0], $this->period[1]]) ->groupBy('label') + ->orderBy('label') ->get(); } @@ -229,57 +228,94 @@ protected function trendsData(): Collection ->selectRaw("$this->type($this->column) as data, " . $this->formatPeriod(self::DAY) . " as label") ->whereYear($this->dateColumn, $this->year) ->whereMonth($this->dateColumn, $this->month) - ->when($this->count > 0, function (QueryBuilder $query) { - $column = self::driver() === 'sqlite' - ? "strftime('%d', ' . $this->dateColumn)" - : "day($this->dateColumn)"; - - return $query->whereBetween(DB::raw($column), [ + ->where(DB::raw($this->formatPeriod(self::WEEK)), $this->week) + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw("day($this->dateColumn)"), $this->day); + }) + ->when($this->count > 1, function (QueryBuilder $query) { + return $query->whereBetween(DB::raw("day($this->dateColumn)"), [ Carbon::now()->subDays($this->count)->day, $this->day ]); }) ->groupBy('label') + ->orderBy('label') + ->get(), + + self::WEEK => $this->builder + ->toBase() + ->selectRaw("$this->type($this->column) as data, " . $this->formatPeriod(self::WEEK) . " as label") + ->whereYear($this->dateColumn, $this->year) + ->whereMonth($this->dateColumn, $this->month) + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw($this->formatPeriod(self::WEEK)), $this->week); + }) + ->when($this->count > 1, function (QueryBuilder $query) { + return $query->whereBetween(DB::raw($this->formatPeriod(self::WEEK)), [ + Carbon::now()->subWeeks($this->count)->week, $this->week + ]); + }) + ->groupBy('label') + ->orderBy('label') ->get(), self::MONTH => $this->builder ->toBase() ->selectRaw("$this->type($this->column) as data, " . $this->formatPeriod(self::MONTH) . " as label") ->whereYear($this->dateColumn, $this->year) - ->when($this->count > 0, function (QueryBuilder $query) { + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw($this->formatPeriod(self::MONTH)), $this->month); + }) + ->when($this->count > 1, function (QueryBuilder $query) { return $query->whereBetween(DB::raw($this->formatPeriod(self::MONTH)), [ - Carbon::now()->subMonths($this->count)->month, $this->month + $this->parseMonth(), $this->month ]); }) ->groupBy('label') + ->orderBy('label') ->get(), self::YEAR => $this->builder ->toBase() ->selectRaw("$this->type($this->column) as data, " . $this->formatPeriod(self::YEAR) . " as label") - ->when($this->count > 0, function (QueryBuilder $query) { + ->when($this->count === 1, function (QueryBuilder $query) { + return $query->where(DB::raw($this->formatPeriod(self::YEAR)), $this->year); + }) + ->when($this->count > 1, function (QueryBuilder $query) { return $query->whereBetween(DB::raw($this->formatPeriod(self::YEAR)), [ Carbon::now()->subYears($this->count)->year, $this->year ]); }) ->groupBy('label') + ->orderBy('label') ->get(), default => [], }; } - protected function driver(): string + protected function parseMonth(): int { - $connection = Config::get('database.default'); - return Config::get("database.connections.$connection.driver"); + $diff = $this->month - Carbon::now()->startOfYear()->month; + + if ($diff < $this->count) { + return Carbon::now()->startOfYear()->month; + } + + return Carbon::now()->subMonths($this->count)->month; + } + + protected function locale(): string + { + return Config::get('app.locale'); } protected function formatPeriod(string $period): string { return match ($period) { - self::DAY => self::driver() === 'sqlite' ? "strftime('%w', $this->dateColumn)" : "weekday($this->dateColumn)", - self::MONTH => self::driver() === 'sqlite' ? "strftime('%m', $this->dateColumn)" : "month($this->dateColumn)", - self::YEAR => self::driver() === 'sqlite' ? "strftime('%Y', $this->dateColumn)" : "year($this->dateColumn)", + self::DAY => "weekday($this->dateColumn)", + self::WEEK => "week($this->dateColumn)", + self::MONTH => "month($this->dateColumn)", + self::YEAR => "year($this->dateColumn)", default => '', }; } @@ -288,15 +324,15 @@ protected function formatDate(array $data): array { return array_map(function ($data) { if ($this->period === self::MONTH) { - $data->label = self::MONTH_NAME[intval($data->label)]; - } else if ($this->period === self::DAY) { - if (self::driver() === 'sqlite') { - $data->label = self::DAY_NAME[intval($data->label)]; - } else { - $data->label = self::DAY_NAME[intval($data->label) + 1] ?? self::DAY_NAME[0]; - } - } else { + $data->label = Carbon::parse($this->year . '-' . $data->label)->locale(self::locale())->monthName; + } elseif ($this->period === self::DAY) { + $data->label = Carbon::parse($this->year . '-' . $this->month . '-' . $data->label)->locale(self::locale())->dayName; + } elseif ($this->period === self::WEEK) { + $data->label = 'Week ' . $data->label; + } elseif ($this->period === self::YEAR) { $data->label = intval($data->label); + } else { + $data->label = Carbon::parse($data->label)->locale(self::locale())->toFormattedDateString(); } return $data;