From f028a8b2a849b3044e466a69c53d0f4fc58fbf56 Mon Sep 17 00:00:00 2001 From: salam Date: Fri, 11 Sep 2026 10:36:16 +0200 Subject: [PATCH] fix update player --- app/Actions/PlayerPerSeason.php | 8 +- app/Console/Commands/Grap.php | 48 +- app/Console/Commands/Grapper/Sofascore.php | 128 ----- app/Console/Commands/Upd.php | 20 + app/Filament/Imports/Helper/ImportDetails.php | 6 +- app/Filament/Imports/Helper/ImportPlayer.php | 17 +- ...LoadPlayer.php => LoadPlayerSelection.php} | 44 +- app/Filament/Pages/Statistic.php | 4 +- app/Filament/Widgets/ChartPlayer.php | 8 +- app/Filament/Widgets/ChartPlayerMoAv.php | 8 +- .../{LoadPlayer.php => LoadPlayerWidgets.php} | 9 +- app/Models/SofaScorePlayerPerSeason.php | 3 + app/Models/StatistikoPlayer.php | 4 +- app/Traits/UpdatePlayerSeasonHelper.php | 488 ++++++++++++++++++ composer.lock | 31 +- ...te_sofa_score_player_per_seasons_table.php | 3 + 16 files changed, 605 insertions(+), 224 deletions(-) delete mode 100644 app/Console/Commands/Grapper/Sofascore.php create mode 100644 app/Console/Commands/Upd.php rename app/Filament/Pages/Selections/{LoadPlayer.php => LoadPlayerSelection.php} (75%) rename app/Filament/Widgets/Data/{LoadPlayer.php => LoadPlayerWidgets.php} (98%) create mode 100644 app/Traits/UpdatePlayerSeasonHelper.php diff --git a/app/Actions/PlayerPerSeason.php b/app/Actions/PlayerPerSeason.php index e03e4684..fda96fd3 100644 --- a/app/Actions/PlayerPerSeason.php +++ b/app/Actions/PlayerPerSeason.php @@ -6,24 +6,18 @@ use App\Models\SofaScorePlayerCache; use App\Models\SofaScorePlayerPerSeason; use App\Models\StatistikoDetail; use App\Models\StatistikoPlayer; -use App\Utilities\Notice; class PlayerPerSeason { public static function run() { - $debug = 0; // when data changes clear player cache SofaScorePlayerCache::truncate(); $res = StatistikoDetail::query()->where('is_active', 1)->select('ss_player_id', 'ss_team_id')->get(); - foreach ($res as $key => $val) { - - if ($debug) { - Notice::note($val->ss_player_id.' - '.$res->count().'/'.$key + 1, 'g'); - } + foreach ($res as $val) { $collection = []; diff --git a/app/Console/Commands/Grap.php b/app/Console/Commands/Grap.php index d13f5465..679b465e 100644 --- a/app/Console/Commands/Grap.php +++ b/app/Console/Commands/Grap.php @@ -2,7 +2,6 @@ namespace App\Console\Commands; -use App\Actions\PlayerPerSeason; use App\Console\Commands\Grapper\Sofascore\Grapper; use App\Console\Commands\Grapper\Sofascore\GrapperTest; use App\Console\Commands\Grapper\Sofascore\Helper\SetStorage; @@ -11,11 +10,14 @@ use App\Console\Commands\Grapper\Sofascore\Rose; use App\Console\Commands\Grapper\Sofascore\SendMail; use App\Console\Commands\Grapper\Sofascore\SettingsToLocal; use App\Console\Commands\Grapper\Sofascore\SqlMover; +use App\Traits\UpdatePlayerSeasonHelper; use App\Utilities\Notice; use Illuminate\Console\Command; class Grap extends Command { + use UpdatePlayerSeasonHelper; + public static $options = []; public static string $storage; @@ -38,29 +40,29 @@ class Grap extends Command self::$options['base_url'] = 'https://www.sofascore.com/'; if (! self::$options['id']) { - $txt = 'PARAMETERS' . PHP_EOL; - $txt .= '--d debug' . PHP_EOL; - $txt .= '--r restart range' . PHP_EOL; - $txt .= '--f force' . PHP_EOL; - $txt .= '--m mail only' . PHP_EOL; - $txt .= '--R read file' . PHP_EOL; - $txt .= '--S save to file' . PHP_EOL; - $txt .= '--folder' . PHP_EOL; - $txt .= '-----------------------------------------------------------' . PHP_EOL; - $txt .= 'FUNCTION' . PHP_EOL; - $txt .= ' 1 grapper DAILY & subDays ( default 3)' . PHP_EOL; - $txt .= ' 2 grapper DATE' . PHP_EOL; - $txt .= ' 3 grapper RANGE' . PHP_EOL; - $txt .= ' 4 grapper ROSE' . PHP_EOL; - $txt .= ' 5 import to excel from folder' . PHP_EOL; - $txt .= ' 6 PlayerPerSeason pre calcuation' . PHP_EOL; - $txt .= ' 7 settings to local' . PHP_EOL; - $txt .= ' 11 move SQL' . PHP_EOL; - $txt .= ' 12 testing url for grapping' . PHP_EOL; + $txt = 'PARAMETERS'.PHP_EOL; + $txt .= '--d debug'.PHP_EOL; + $txt .= '--r restart range'.PHP_EOL; + $txt .= '--f force'.PHP_EOL; + $txt .= '--m mail only'.PHP_EOL; + $txt .= '--R read file'.PHP_EOL; + $txt .= '--S save to file'.PHP_EOL; + $txt .= '--folder'.PHP_EOL; + $txt .= '-----------------------------------------------------------'.PHP_EOL; + $txt .= 'FUNCTION'.PHP_EOL; + $txt .= ' 1 grapper DAILY & subDays ( default 3)'.PHP_EOL; + $txt .= ' 2 grapper DATE'.PHP_EOL; + $txt .= ' 3 grapper RANGE'.PHP_EOL; + $txt .= ' 4 grapper ROSE'.PHP_EOL; + $txt .= ' 5 import to excel from folder'.PHP_EOL; + $txt .= ' 6 update PlayerPerSeason'.PHP_EOL; + $txt .= ' 7 settings to local'.PHP_EOL; + $txt .= ' 11 move SQL'.PHP_EOL; + $txt .= ' 12 testing url for grapping'.PHP_EOL; Notice::note($txt, 'b', 1); } - Notice::note('Task Id ' . self::$options['id'], 'gb'); + Notice::note('Task Id '.self::$options['id'], 'gb'); switch (self::$options['id']) { @@ -70,7 +72,7 @@ class Grap extends Command // MoveToArchive::move(1); // with --m just resend mail - if (!self::$options['m']) { + if (! self::$options['m']) { Grapper::load(1); MakeExcel::create(); } @@ -104,7 +106,7 @@ class Grap extends Command break; case 6: - PlayerPerSeason::run(); + $this->update(1000000); break; case 7: diff --git a/app/Console/Commands/Grapper/Sofascore.php b/app/Console/Commands/Grapper/Sofascore.php deleted file mode 100644 index ae665ab5..00000000 --- a/app/Console/Commands/Grapper/Sofascore.php +++ /dev/null @@ -1,128 +0,0 @@ -first(); - if ($task) { - $storage_name = Strings::clean($task->name); - - switch ($task->id) { - case 1: - self::$storage = $sofascore_data . $storage_name; - self::$folder = Carbon::now()->subDays(3)->format('Y-m-d'); - break; - case 2: - self::$storage = $sofascore_data . $storage_name . '/' . $task->day_from; - self::$zipfile = $sofascore_data . $storage_name . '/' . $task->day_from . '.zip'; - break; - case 3: - self::$storage = $sofascore_data . $storage_name . '-' . $task->day_from . '-' . $task->day_to; - self::$zipfile = $sofascore_data . $storage_name . '-' . $task->day_from . '-' . $task->day_to . '.zip'; - break; - case 4: - self::$storage = $sofascore_data . $storage_name . '/rosa-' . Carbon::now()->format('Y-m-d'); - self::$zipfile = $sofascore_data . $storage_name . '/rosa-' . Carbon::now()->format('Y-m-d') . '.zip'; - break; - - default: - self::$storage = $sofascore_data . $storage_name; - self::$zipfile = $sofascore_data . $storage_name . '/archive.zip'; - break; - } - } else if ($folder) { - self::$storage = $sofascore_data; - self::$folder = $folder; - } - } -} diff --git a/app/Console/Commands/Upd.php b/app/Console/Commands/Upd.php new file mode 100644 index 00000000..214b8773 --- /dev/null +++ b/app/Console/Commands/Upd.php @@ -0,0 +1,20 @@ +update($playerCount); + } +} diff --git a/app/Filament/Imports/Helper/ImportDetails.php b/app/Filament/Imports/Helper/ImportDetails.php index e3a12c40..1ef1f558 100644 --- a/app/Filament/Imports/Helper/ImportDetails.php +++ b/app/Filament/Imports/Helper/ImportDetails.php @@ -2,14 +2,16 @@ namespace App\Filament\Imports\Helper; -use App\Actions\PlayerPerSeason; use App\Models\StatistikoDetail; +use App\Traits\UpdatePlayerSeasonHelper; use Illuminate\Support\Carbon; use Illuminate\Support\LazyCollection; use Spatie\SimpleExcel\SimpleExcelReader; class ImportDetails { + use UpdatePlayerSeasonHelper; + public static function run(string $filePath): void { $now = now()->format('Y-m-d H:i:s'); @@ -62,6 +64,6 @@ class ImportDetails StatistikoDetail::where('updated_at', '!=', $now)->update(['is_active' => 0]); /**update statistic player tabel */ - PlayerPerSeason::run(); + UpdatePlayerSeasonHelper::update(1000000); } } diff --git a/app/Filament/Imports/Helper/ImportPlayer.php b/app/Filament/Imports/Helper/ImportPlayer.php index 8ed10957..e5dd096c 100644 --- a/app/Filament/Imports/Helper/ImportPlayer.php +++ b/app/Filament/Imports/Helper/ImportPlayer.php @@ -2,13 +2,15 @@ namespace App\Filament\Imports\Helper; -use App\Actions\PlayerPerSeason; use App\Models\StatistikoPlayer; +use App\Traits\UpdatePlayerSeasonHelper; use Illuminate\Support\LazyCollection; use Spatie\SimpleExcel\SimpleExcelReader; class ImportPlayer { + use UpdatePlayerSeasonHelper; + public static function run(string $filePath): void { $fieldnames = [ @@ -78,10 +80,10 @@ class ImportPlayer 'penaltyshoutout_missed', 'player_position', 'playerrating_value', - 'tm_player_id' + 'tm_player_id', ]; - SimpleExcelReader::create(storage_path('app/public/' . $filePath)) + SimpleExcelReader::create(storage_path('app/public/'.$filePath)) ->getRows() ->chunk(1000) ->each(function (LazyCollection $chunk) use ($fieldnames) { @@ -95,12 +97,13 @@ class ImportPlayer $update_array = []; foreach ($fieldnames as $key => $name) { - if ($key == 1) + if ($key == 1) { $match_id = (int) $row[$key]; - else if ($key == 2) + } elseif ($key == 2) { $ss_player_id = (int) $row[$key]; - else + } else { $update_array[$name] = $row[$key]; + } } StatistikoPlayer::updateOrCreate( @@ -114,6 +117,6 @@ class ImportPlayer }); /**update statistic player tabel */ - PlayerPerSeason::run(); + UpdatePlayerSeasonHelper::update(1000000); } } diff --git a/app/Filament/Pages/Selections/LoadPlayer.php b/app/Filament/Pages/Selections/LoadPlayerSelection.php similarity index 75% rename from app/Filament/Pages/Selections/LoadPlayer.php rename to app/Filament/Pages/Selections/LoadPlayerSelection.php index 6f5565aa..122f6764 100644 --- a/app/Filament/Pages/Selections/LoadPlayer.php +++ b/app/Filament/Pages/Selections/LoadPlayerSelection.php @@ -3,21 +3,20 @@ namespace App\Filament\Pages\Selections; use App\Filament\Pages\Selections\Helper\GetSeason; -use App\Filament\Pages\Selections\LoadPositions; use App\Models\SofaScorePlayerCache; use App\Models\SofaScorePlayerPerSeason; use App\Models\StatistikoEvent; -class LoadPlayer +class LoadPlayerSelection { - public static function load(int|null $team_id, string|null $team_position, int|null $sofascore_category_id, array|null $season, string|null $seasonmode) + public static function load(?int $team_id, ?string $team_position, ?int $sofascore_category_id, ?array $season, ?string $seasonmode) { $season = GetSeason::load($season, $seasonmode); - $hash = hash('crc32', $team_id . $team_position . $sofascore_category_id . serialize($season)); + $hash = hash('crc32', $team_id.$team_position.$sofascore_category_id.serialize($season)); $cached_players = SofaScorePlayerCache::where('hash', $hash)->value('players'); if ($cached_players) { - // return $cached_players; + return $cached_players; } $matches = StatistikoEvent::whereIn('season', $season) @@ -33,7 +32,6 @@ class LoadPlayer } })->pluck('match_id')->toArray(); - $matches_count = count($matches); $positions_map = LoadPositions::load(); @@ -44,7 +42,7 @@ class LoadPlayer foreach ($player as $val) { if ($val->details) { $player_id = $val->ss_player_id; - if (!isset($arr[$player_id])) { + if (! isset($arr[$player_id])) { $arr[$player_id]['player_name'] = $val->details->player_name; $arr[$player_id]['events_played_home'] = $val->events_played_home; $arr[$player_id]['events_played_away'] = $val->events_played_away; @@ -80,43 +78,43 @@ class LoadPlayer break; } - $colorlevel = round($matches_count / 3); if (in_array($val['player_position'], $positions_map['MIDFIELD'])) { $position = 'MIDFIELD'; - } else if (in_array($val['player_position'], $positions_map['DEFENCE'])) { + } elseif (in_array($val['player_position'], $positions_map['DEFENCE'])) { $position = 'DEFENCE'; - } else if (in_array($val['player_position'], $positions_map['ATTACK'])) { + } elseif (in_array($val['player_position'], $positions_map['ATTACK'])) { $position = 'ATTACK'; - } else if (in_array($val['player_position'], $positions_map['GOALKEEPER'])) { + } elseif (in_array($val['player_position'], $positions_map['GOALKEEPER'])) { $position = 'GOALKEEPER'; } else { $position = 'NO SPECIFIC POSITION'; } - $infostring = $events_played . ' Ev'; - if ($val['preferred_foot']) - $infostring .= ' - ' . $val['preferred_foot']; - if ($events_played) - $infostring .= ' - ' . round($minues_played / $events_played) . 'min'; + $infostring = $events_played.' Ev'; + if ($val['preferred_foot']) { + $infostring .= ' - '.$val['preferred_foot']; + } + if ($events_played) { + $infostring .= ' - '.round($minues_played / $events_played).'min'; + } if ($colorlevel > 1 && $events_played < $colorlevel) { - $result[$position][$player_id] = '' . $val['player_name'] . ' ' . $position . '
' . $infostring . '
'; - } else if ($events_played < $colorlevel * 2) { - $result[$position][$player_id] = '' . $val['player_name'] . ' ' . $position . '
' . $infostring . '
'; + $result[$position][$player_id] = ''.$val['player_name'].' '.$position.'
'.$infostring.'
'; + } elseif ($events_played < $colorlevel * 2) { + $result[$position][$player_id] = ''.$val['player_name'].' '.$position.'
'.$infostring.'
'; } else { - $result[$position][$player_id] = '' . $val['player_name'] . ' ' . $position . '
' . $infostring . '
'; + $result[$position][$player_id] = ''.$val['player_name'].' '.$position.'
'.$infostring.'
'; } } - if ($result) + if ($result) { SofaScorePlayerCache::updateOrCreate(['hash' => $hash], ['players' => $result]); + } return $result; } - - public static function convertToHoursMins(int $time, $format = '%02dh:%02dm') { if ($time < 1) { diff --git a/app/Filament/Pages/Statistic.php b/app/Filament/Pages/Statistic.php index a853bec0..0af81863 100644 --- a/app/Filament/Pages/Statistic.php +++ b/app/Filament/Pages/Statistic.php @@ -5,7 +5,7 @@ namespace App\Filament\Pages; use App\Actions\LoadSofaScoreCategory; use App\Actions\LoadStatisticFixturesLeaguesOptions; use App\Actions\LoadStatisticFixturesMatchOptions; -use App\Filament\Pages\Selections\LoadPlayer; +use App\Filament\Pages\Selections\LoadPlayerSelection; use App\Models\SofaScoreCategory; use App\Models\SofaScoreRosa; use App\Models\StatisticFixtures; @@ -347,7 +347,7 @@ class Statistic extends BaseDashboard $season = $get('season'); $seasonmode = $get('seasonmode'); - return LoadPlayer::load($team_id, $team_position, $sofascore_category_id, $season, $seasonmode); + return LoadPlayerSelection::load($team_id, $team_position, $sofascore_category_id, $season, $seasonmode); }), Select::make('player_on_field') ->live() diff --git a/app/Filament/Widgets/ChartPlayer.php b/app/Filament/Widgets/ChartPlayer.php index 88adbfe0..bd988e37 100644 --- a/app/Filament/Widgets/ChartPlayer.php +++ b/app/Filament/Widgets/ChartPlayer.php @@ -3,7 +3,7 @@ namespace App\Filament\Widgets; use App\Filament\Widgets\Data\HelperStatisticType; -use App\Filament\Widgets\Data\LoadPlayer; +use App\Filament\Widgets\Data\LoadPlayerWidgets; use Filament\Support\RawJs; use Filament\Widgets\ChartWidget; use Illuminate\Support\HtmlString; @@ -38,16 +38,16 @@ class ChartPlayer extends ChartWidget c0.211,0.1782,0.5264,0.1516,0.7046-0.0593C13.0037,6.5523,13.0018,6.2761,12.84,6.09z"> - ' . $this->player . ' + '.$this->player.' - ' . $this->type . ' + '.$this->type.' '); } protected function getData(): array { - $data = LoadPlayer::load($this->filters, true); + $data = LoadPlayerWidgets::load($this->filters, true); if ($data) { $this->player = $data['player_name'] ?? ''; diff --git a/app/Filament/Widgets/ChartPlayerMoAv.php b/app/Filament/Widgets/ChartPlayerMoAv.php index 11826096..09b594fc 100644 --- a/app/Filament/Widgets/ChartPlayerMoAv.php +++ b/app/Filament/Widgets/ChartPlayerMoAv.php @@ -4,7 +4,7 @@ namespace App\Filament\Widgets; use App\Filament\Widgets\Data\HelperMoveingAverage; use App\Filament\Widgets\Data\HelperStatisticType; -use App\Filament\Widgets\Data\LoadPlayer; +use App\Filament\Widgets\Data\LoadPlayerWidgets; use Filament\Support\RawJs; use Filament\Widgets\ChartWidget; use Illuminate\Support\HtmlString; @@ -39,16 +39,16 @@ class ChartPlayerMoAv extends ChartWidget c0.211,0.1782,0.5264,0.1516,0.7046-0.0593C13.0037,6.5523,13.0018,6.2761,12.84,6.09z"> - ' . $this->player . ' + '.$this->player.' - ' . $this->type . ' Moving Average + '.$this->type.' Moving Average '); } protected function getData(): array { - $data = LoadPlayer::load($this->filters, false); + $data = LoadPlayerWidgets::load($this->filters, false); if ($data) { $this->player = $data['player_name'] ?? ''; diff --git a/app/Filament/Widgets/Data/LoadPlayer.php b/app/Filament/Widgets/Data/LoadPlayerWidgets.php similarity index 98% rename from app/Filament/Widgets/Data/LoadPlayer.php rename to app/Filament/Widgets/Data/LoadPlayerWidgets.php index 51ace86a..446a786b 100644 --- a/app/Filament/Widgets/Data/LoadPlayer.php +++ b/app/Filament/Widgets/Data/LoadPlayerWidgets.php @@ -8,7 +8,7 @@ use App\Models\StatistikoEvent; use App\Models\StatistikoPlayer; use Illuminate\Support\Carbon; -class LoadPlayer +class LoadPlayerWidgets { public static $last_query = []; @@ -29,7 +29,6 @@ class LoadPlayer $player_on_field = $filter['player_on_field'] ?? 'all'; - $season = $filter['season']; $seasonmode = $filter['seasonmode']; $season = GetSeason::load($season, $seasonmode); @@ -110,7 +109,7 @@ class LoadPlayer if (isset($value->event->starttime)) { - $key = round($value->event->starttime->diffInDays(Carbon::now())); + $key = (int) round($value->event->starttime->diffInDays(Carbon::now())); switch ($statistic_type) { case 'gls': @@ -140,8 +139,6 @@ class LoadPlayer } } - - krsort($tmp); $dataset = []; $labels = []; @@ -153,8 +150,6 @@ class LoadPlayer $avverage = array_sum($dataset) / count($dataset); $dataset2 = array_fill(0, count($dataset), $avverage); - - $data = [ 'datasets' => [ [ diff --git a/app/Models/SofaScorePlayerPerSeason.php b/app/Models/SofaScorePlayerPerSeason.php index c82cdbe9..1ac0678a 100644 --- a/app/Models/SofaScorePlayerPerSeason.php +++ b/app/Models/SofaScorePlayerPerSeason.php @@ -10,6 +10,9 @@ class SofaScorePlayerPerSeason extends Model 'ss_player_id', 'ss_team_id', 'season', + 'total_events', + 'total_min', + 'average_min', 'events_played_home', 'events_played_away', 'minutes_played_home', diff --git a/app/Models/StatistikoPlayer.php b/app/Models/StatistikoPlayer.php index 872b27ed..8b54caf0 100644 --- a/app/Models/StatistikoPlayer.php +++ b/app/Models/StatistikoPlayer.php @@ -86,9 +86,9 @@ class StatistikoPlayer extends Model // AGGIORNAMENTO // tm_player_id - // penaltyshoutout_missed + // penaltyshoutout_missed // penaltyshoutout_scored - // ingamepenalty_missed, + // ingamepenalty_missed, // card_yellow // card_yellowred // card_red diff --git a/app/Traits/UpdatePlayerSeasonHelper.php b/app/Traits/UpdatePlayerSeasonHelper.php new file mode 100644 index 00000000..e676f782 --- /dev/null +++ b/app/Traits/UpdatePlayerSeasonHelper.php @@ -0,0 +1,488 @@ +options(); + $this->debug = $tmp['d'] ?? 0; + + if ($this->debug) { + Notice::note('Update Player Season', 'g'); + } + + if ($this->debug) { + $playerCount = $this->selectPlayer(); + } else { + $playerCount = 1000000; + } + + if ($this->debug) { + $this->startBenchmark(); + } + + try { + $this->update($playerCount); + } catch (\Exception $e) { + $this->error(get_class($e).' '.Str::of($e->getMessage())->limit(100)->value()); + } + + if ($this->debug) { + $this->endBenchmark(); + } + } + + // Flight::upsert([ + // ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], + // ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] + // ], uniqueBy: ['departure', 'destination'], update: ['price']); + + protected function selectPlayer(): string + { + $file = select( + label: 'How many players to update?', + options: ['100 Player', '1k Player', '10k Player', '100k Player', '1m Player'] + ); + + return match ($file) { + '100 Player' => 100, + '1k Player' => 1000, + '10k Player' => 10000, + '100k Player' => 100000, + '1m Player' => 1000000, + }; + } + + protected function startBenchmark(string $table = 'sofa_score_player_per_seasons'): void + { + $this->startRowCount = DB::table($table)->count(); + $this->benchmarkStartTime = microtime(true); + $this->benchmarkStartMemory = memory_get_usage(); + DB::enableQueryLog(); + $this->startQueries = DB::select("SHOW SESSION STATUS LIKE 'Questions'")[0]->Value; + } + + protected function endBenchmark(string $table = 'sofa_score_player_per_seasons'): void + { + $executionTime = microtime(true) - $this->benchmarkStartTime; + $memoryUsage = round((memory_get_usage() - $this->benchmarkStartMemory) / 1024 / 1024, 2); + $queriesCount = DB::select("SHOW SESSION STATUS LIKE 'Questions'")[0]->Value - $this->startQueries - 1; // Subtract the Questions query itself + + // Get row count after we've stopped tracking queries + $rowDiff = DB::table($table)->count() - $this->startRowCount; + + $formattedTime = match (true) { + $executionTime >= 60 => sprintf('%dm %ds', floor($executionTime / 60), $executionTime % 60), + $executionTime >= 1 => round($executionTime, 2).'s', + default => round($executionTime * 1000).'ms', + }; + + $this->newLine(); + $this->line(sprintf( + '⚡ TIME: %s MEM: %sMB SQL: %s ROWS: %s ', + $formattedTime, + $memoryUsage, + number_format($queriesCount), + number_format($rowDiff) + )); + $this->newLine(); + } + + protected function update(int $playerCount) + { + $res_player = StatistikoDetail::query()->where('is_active', 1)->limit($playerCount)->pluck('ss_player_id')->toArray(); + + $chunks = array_chunk($res_player, 1000); + + foreach ($chunks as $chunk) { + + $res = StatistikoPlayer::with('event')->with('details')->whereIn('ss_player_id', $chunk)->orderBy('ss_player_id')->get(); + + $collection = []; + foreach ($res as $val) { + + $ss_player_id = (int) $val->ss_player_id; + $activeteam_id = (int) $val->activeteam_id; + $minutes_played = (int) $val->minutes_played; + $ss_team_id = (int) $val->details->ss_team_id; + $season = $val->event->season; + $home_id = (int) $val->event->home_id; + $away_id = (int) $val->event->away_id; + + if (! isset($collection[$season][$ss_player_id])) { + $this->array_template($collection, $ss_player_id, $season); + } + if (! isset($collection['total'][$ss_player_id])) { + $this->array_template($collection, $ss_player_id, 'total'); + } + + if ($minutes_played > 0) { + if ($activeteam_id == $home_id) { + $collection[$season][$ss_player_id]['events_played_home'] += 1; + $collection[$season][$ss_player_id]['minutes_played_home'] += $minutes_played; + $collection['total'][$ss_player_id]['events_played_home'] += 1; + $collection['total'][$ss_player_id]['minutes_played_home'] += $minutes_played; + } elseif ($activeteam_id == $away_id) { + $collection[$season][$ss_player_id]['events_played_away'] += 1; + $collection[$season][$ss_player_id]['minutes_played_away'] += $minutes_played; + $collection['total'][$ss_player_id]['minutes_played_away'] += 1; + $collection['total'][$ss_player_id]['minutes_played_away'] += $minutes_played; + } + + $cards = $val->card_yellow + $val->card_yellowred + $val->card_red; + + $collection[$season][$ss_player_id]['ss_player_id'] = $ss_player_id; + $collection[$season][$ss_player_id]['ss_team_id'] = $ss_team_id; + $collection[$season][$ss_player_id]['season'] = $season; + $collection[$season][$ss_player_id]['total_events']++; + $collection[$season][$ss_player_id]['total_min'] += $minutes_played; + $collection[$season][$ss_player_id]['average_min'] = round($collection[$season][$ss_player_id]['total_min'] / $collection[$season][$ss_player_id]['total_events']); + $collection[$season][$ss_player_id]['goals'] += $val->goals; + $collection[$season][$ss_player_id]['fouls'] += $val->fouls; + $collection[$season][$ss_player_id]['cards'] += $cards; + $collection[$season][$ss_player_id]['offsides'] += $val->offsides; + $collection[$season][$ss_player_id]['shots_on_target'] += $val->shots_on_target; + $collection[$season][$ss_player_id]['shots_off_target'] += $val->shots_off_target; + + $collection['total'][$ss_player_id]['ss_player_id'] = $ss_player_id; + $collection['total'][$ss_player_id]['ss_team_id'] = $ss_team_id; + $collection['total'][$ss_player_id]['season'] = 'total'; + $collection['total'][$ss_player_id]['total_events']++; + $collection['total'][$ss_player_id]['total_min'] += $minutes_played; + $collection['total'][$ss_player_id]['average_min'] = round($collection['total'][$ss_player_id]['total_min'] / $collection['total'][$ss_player_id]['total_events']); + $collection['total'][$ss_player_id]['goals'] += $val->goals; + $collection['total'][$ss_player_id]['fouls'] += $val->fouls; + $collection['total'][$ss_player_id]['cards'] += $cards; + $collection['total'][$ss_player_id]['offsides'] += $val->offsides; + $collection['total'][$ss_player_id]['shots_on_target'] += $val->shots_on_target; + $collection['total'][$ss_player_id]['shots_off_target'] += $val->shots_off_target; + } + } + + $collection_1d = []; + foreach ($collection as $arr) { + foreach ($arr as $row) { + $collection_1d[] = $row; + } + } + SofaScorePlayerPerSeason::upsert( + $collection_1d, + [ + 'ss_player_id', + 'ss_team_id', + 'season', + ], + [ + 'total_events', + 'total_min', + 'average_min', + 'events_played_home', + 'events_played_away', + 'minutes_played_home', + 'minutes_played_away', + 'goals', + 'fouls', + 'offsides', + 'shots_on_target', + 'shots_off_target', + 'cards', + ] + ); + } + } + + private function array_template(array &$collection, int $ss_player_id, string $season) + { + $collection[$season][$ss_player_id]['ss_player_id'] = 0; + $collection[$season][$ss_player_id]['ss_team_id'] = 0; + $collection[$season][$ss_player_id]['season'] = ''; + $collection[$season][$ss_player_id]['total_events'] = 0; + $collection[$season][$ss_player_id]['total_min'] = 0; + $collection[$season][$ss_player_id]['average_min'] = 0; + $collection[$season][$ss_player_id]['events_played_home'] = 0; + $collection[$season][$ss_player_id]['events_played_away'] = 0; + $collection[$season][$ss_player_id]['minutes_played_home'] = 0; + $collection[$season][$ss_player_id]['minutes_played_away'] = 0; + $collection[$season][$ss_player_id]['goals'] = 0; + $collection[$season][$ss_player_id]['fouls'] = 0; + $collection[$season][$ss_player_id]['offsides'] = 0; + $collection[$season][$ss_player_id]['shots_on_target'] = 0; + $collection[$season][$ss_player_id]['shots_off_target'] = 0; + $collection[$season][$ss_player_id]['cards'] = 0; + } + + private function import08ManualStreamingWithPdo(string $filePath): void + { + // 100 7ms / 0MB + // 1K 78ms / 0MB + // 10K 328ms / 0MB + // 100K 2.9s / 0MB + // 1M 28s / 0MB + $data = []; + $handle = fopen($filePath, 'rb'); + fgetcsv($handle); // skip header + $now = now()->format('Y-m-d H:i:s'); + $pdo = DB::connection()->getPdo(); + + while (($row = fgetcsv($handle)) !== false) { + $data[] = [ + 'custom_id' => $row[0], + 'name' => $row[1], + 'email' => $row[2], + 'company' => $row[3], + 'city' => $row[4], + 'country' => $row[5], + 'birthday' => $row[6], + 'created_at' => $now, + 'updated_at' => $now, + ]; + + if (count($data) === 1000) { + // Build the SQL query for the chunk + $columns = array_keys($data[0]); + $placeholders = rtrim(str_repeat('(?,?,?,?,?,?,?,?,?),', count($data)), ','); + + $sql = 'INSERT INTO customers ('.implode(',', $columns).') VALUES '.$placeholders; + + // Flatten the data array for the query + $values = []; + foreach ($data as $row) { + $values = array_merge($values, array_values($row)); + } + + $pdo->prepare($sql)->execute($values); + $data = []; + } + } + + if (! empty($data)) { + $columns = array_keys($data[0]); + $placeholders = rtrim(str_repeat('(?,?,?,?,?,?,?,?,?),', count($data)), ','); + + $sql = 'INSERT INTO customers ('.implode(',', $columns).') VALUES '.$placeholders; + + $values = []; + foreach ($data as $row) { + $values = array_merge($values, array_values($row)); + } + + $pdo->prepare($sql)->execute($values); + } + + fclose($handle); + } + + private function import09PDOPrepared(string $filePath): void + { + // Direct database connection with prepared statements + // 100 41ms / 0 MB + // 1K 237ms / + // 10K 2.21s + // 100K 25.27s + // 1M 4m43s + // 2M + $now = now()->format('Y-m-d H:i:s'); + $handle = fopen($filePath, 'r'); + fgets($handle); // skip header + + try { + $pdo = DB::connection()->getPdo(); + $stmt = $pdo->prepare(' + INSERT INTO customers (custom_id, name, email, company, city, country, birthday, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + '); + + while (($row = fgetcsv($handle)) !== false) { + $stmt->execute([ + $row[0], + $row[1], + $row[2], + $row[3], + $row[4], + $row[5], + $row[6], + $now, + $now, + ]); + } + } finally { + fclose($handle); + } + } + + private function import10PDOPreparedChunked(string $filePath): void + { + // Direct database connection with prepared statements + // 100 12ms / 0.15MB + // 1K 49ms / 0.74MB + // 10K 222ms / 0.74MB + // 100K 1.5s / 0.74MB + // 1M 15.3s / 0.74MB + // 2M 24s / 0.74MN + $now = now()->format('Y-m-d H:i:s'); + $handle = fopen($filePath, 'r'); + fgetcsv($handle); // skip header + $chunkSize = 500; + $chunks = []; + + try { + $stmt = $this->prepareChunkedStatement($chunkSize); + + while (($row = fgetcsv($handle)) !== false) { + $chunks = array_merge($chunks, [ + $row[0], $row[1], $row[2], $row[3], $row[4], + $row[5], $row[6], $now, $now, + ]); + + if (count($chunks) === $chunkSize * 9) { // 9 columns + $stmt->execute($chunks); + $chunks = []; + } + } + + // Handle remaining records + if (! empty($chunks)) { + $remainingRows = count($chunks) / 9; + $stmt = $this->prepareChunkedStatement($remainingRows); + $stmt->execute($chunks); + } + } finally { + fclose($handle); + } + } + + private function import11Concurrent(string $filePath): void + { + // 100 168ms + // 1K 172ms + // 10K 234ms + // 100K 595ms + // 1M 4.36s + // 2M 8.8s + $now = now()->format('Y-m-d H:i:s'); + $numberOfProcesses = 10; + + $tasks = []; + for ($i = 0; $i < $numberOfProcesses; $i++) { + $tasks[] = function () use ($filePath, $i, $numberOfProcesses, $now) { + DB::reconnect(); + + $handle = fopen($filePath, 'r'); + fgets($handle); // Skip header + $currentLine = 0; + $customers = []; + + while (($line = fgets($handle)) !== false) { + // Each process takes every Nth line + if ($currentLine++ % $numberOfProcesses !== $i) { + continue; + } + + $row = str_getcsv($line); + $customers[] = [ + 'custom_id' => $row[0], + 'name' => $row[1], + 'email' => $row[2], + 'company' => $row[3], + 'city' => $row[4], + 'country' => $row[5], + 'birthday' => $row[6], + 'created_at' => $now, + 'updated_at' => $now, + ]; + + if (count($customers) === 1000) { + DB::table('customers')->insert($customers); + $customers = []; + } + } + + if (! empty($customers)) { + DB::table('customers')->insert($customers); + } + + fclose($handle); + + return true; + }; + } + + Concurrency::run($tasks); + + } + + private function import12LoadDataInfile(string $filePath): void + { + // MySQL specific, fastest approach + // 100 10ms / 0MB + // 1K 29ms / 0MB + // 10K 115ms / 0MB + // 100K 567ms / 0MB + // 1M 5s / 0MB + // 2M 11s / 0MB + $pdo = DB::connection()->getPdo(); + $pdo->setAttribute(PDO::MYSQL_ATTR_LOCAL_INFILE, true); + + $filepath = str_replace('\\', '/', $filePath); + + $query = <<exec($query); + } + + private function prepareChunkedStatement($chunkSize): PDOStatement + { + $rowPlaceholders = '(?, ?, ?, ?, ?, ?, ?, ?, ?)'; + $placeholders = implode(',', array_fill(0, $chunkSize, $rowPlaceholders)); + + return DB::connection()->getPdo()->prepare(" + INSERT INTO customers (custom_id, name, email, company, city, country, birthday, created_at, updated_at) + VALUES {$placeholders} + "); + } +} diff --git a/composer.lock b/composer.lock index c5091aa5..8284a0fd 100644 --- a/composer.lock +++ b/composer.lock @@ -4426,29 +4426,30 @@ }, { "name": "maxmind-db/reader", - "version": "v1.13.1", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", - "reference": "2194f58d0f024ce923e685cdf92af3daf9951908" + "reference": "f3c92f68b3bec42a9aa780368399e03dc6b91e89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/2194f58d0f024ce923e685cdf92af3daf9951908", - "reference": "2194f58d0f024ce923e685cdf92af3daf9951908", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/f3c92f68b3bec42a9aa780368399e03dc6b91e89", + "reference": "f3c92f68b3bec42a9aa780368399e03dc6b91e89", "shasum": "" }, "require": { "php": ">=7.2" }, "conflict": { - "ext-maxminddb": "<1.11.1 || >=2.0.0" + "ext-maxminddb": "<1.14.0 || >=2.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "3.*", "phpstan/phpstan": "*", "phpunit/phpunit": ">=8.0.0,<10.0.0", - "squizlabs/php_codesniffer": "4.*" + "squizlabs/php_codesniffer": "4.*", + "symfony/polyfill-php80": "^1.33" }, "suggest": { "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", @@ -4470,7 +4471,7 @@ { "name": "Gregory J. Oschwald", "email": "goschwald@maxmind.com", - "homepage": "https://www.maxmind.com/" + "homepage": "https://www.maxmind.com/en/home" } ], "description": "MaxMind DB Reader API", @@ -4484,9 +4485,9 @@ ], "support": { "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues", - "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.13.1" + "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.14.0" }, - "time": "2025-11-21T22:24:26+00:00" + "time": "2026-09-10T22:02:38+00:00" }, { "name": "maxmind/web-service-common", @@ -4712,16 +4713,16 @@ }, { "name": "mongodb/mongodb", - "version": "2.4.1", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/mongodb/mongo-php-library", - "reference": "b72cc2ecf4a11d3871f78a119a8f3cc7ab6d6c29" + "reference": "1fbdc2ffff18a1ad6d92043b40060e4f546982ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b72cc2ecf4a11d3871f78a119a8f3cc7ab6d6c29", - "reference": "b72cc2ecf4a11d3871f78a119a8f3cc7ab6d6c29", + "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/1fbdc2ffff18a1ad6d92043b40060e4f546982ff", + "reference": "1fbdc2ffff18a1ad6d92043b40060e4f546982ff", "shasum": "" }, "require": { @@ -4737,7 +4738,7 @@ "require-dev": { "doctrine/coding-standard": "^12.0", "phpunit/phpunit": "^10.5.35", - "rector/rector": "^2.3.4", + "rector/rector": "~2.5.9", "squizlabs/php_codesniffer": "^3.7", "vimeo/psalm": "~6.14.2" }, @@ -4781,7 +4782,7 @@ "mongodb", "persistence" ], - "time": "2026-08-27T14:43:15+00:00" + "time": "2026-09-10T15:46:23+00:00" }, { "name": "monolog/monolog", diff --git a/database/migrations/2026_06_27_091506_create_sofa_score_player_per_seasons_table.php b/database/migrations/2026_06_27_091506_create_sofa_score_player_per_seasons_table.php index c41d40a6..d7e0a259 100644 --- a/database/migrations/2026_06_27_091506_create_sofa_score_player_per_seasons_table.php +++ b/database/migrations/2026_06_27_091506_create_sofa_score_player_per_seasons_table.php @@ -16,6 +16,9 @@ return new class extends Migration $table->integer('ss_player_id')->index(); $table->integer('ss_team_id')->index(); $table->string('season')->index(); + $table->integer('total_events')->default(0)->nullable(); + $table->integer('total_min')->default(0)->nullable(); + $table->integer('average_min')->default(0)->nullable(); $table->integer('events_played_home')->default(0)->nullable(); $table->integer('events_played_away')->default(0)->nullable(); $table->integer('minutes_played_home')->default(0)->nullable();