From 33b4e2a1a6a9efc59f2c18ca28eaa44494671b2e Mon Sep 17 00:00:00 2001 From: Jonathan Danse Date: Wed, 8 Jul 2026 15:02:51 +0200 Subject: [PATCH 1/6] feat(dto): add slug and contributors list to Company --- src/DTO/Company.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/DTO/Company.php b/src/DTO/Company.php index 4394567..f6b7bd0 100644 --- a/src/DTO/Company.php +++ b/src/DTO/Company.php @@ -26,6 +26,12 @@ class Company * @var array */ public array $mergedContributionsByVersion = []; + /** + * @var string[] + */ + public array $contributors = []; + + public readonly string $slug; public function __construct( public readonly string $name, @@ -40,15 +46,24 @@ public function __construct( public readonly string $avatarUrl, public readonly string $htmlUrl, ) { + $this->slug = self::slugify($this->name); + } + + private static function slugify(string $value): string + { + $ascii = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value); + $ascii = preg_replace('/[^A-Za-z0-9]+/', '-', $ascii ?: $value) ?? $value; + return strtolower(trim($ascii, '-')) ?: 'company'; } /** - * @return array|float|int|string> + * @return array|float|int|string|string[]> */ public function toArray(bool $rankByContributions): array { return [ 'name' => $this->name, + 'slug' => $this->slug, 'rank' => $rankByContributions ? $this->rankByContributions : $this->rankByPR, 'rank_contributions' => $this->rankByContributions, 'rank_pull_requests' => $this->rankByPR, @@ -62,6 +77,7 @@ public function toArray(bool $rankByContributions): array 'contributions_percent' => $this->contributionsPercent, 'avatar_url' => $this->avatarUrl, 'html_url' => $this->htmlUrl, + 'contributors' => array_values(array_unique($this->contributors)), ]; } } From 553f3a703c7f37172d47b84d0e7567323f77dbfc Mon Sep 17 00:00:00 2001 From: Jonathan Danse Date: Thu, 9 Jul 2026 08:43:22 +0200 Subject: [PATCH 2/6] feat(topcompanies): add byYear sibling maps at contributor level and populate Company contributors Co-Authored-By: Claude Opus 4.7 --- src/Command/GenerateTopCompaniesCommand.php | 46 ++++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/Command/GenerateTopCompaniesCommand.php b/src/Command/GenerateTopCompaniesCommand.php index 68e242c..cfeceef 100644 --- a/src/Command/GenerateTopCompaniesCommand.php +++ b/src/Command/GenerateTopCompaniesCommand.php @@ -84,17 +84,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int unset($contributors['updatedAt']); foreach ($contributors as $key => $contributor) { $contributors[$key]['mergedPullRequests'] = 0; + $contributors[$key]['mergedPullRequestsByYear'] = []; foreach (self::REPOSITORIES_CATEGORIES as $section => $repositories) { $contributors[$key]['categories'][$section] = [ 'total' => 0, + 'byYear' => [], 'repositories' => [], + 'repositoriesByYear' => [], ]; foreach ($repositories as $repository) { $categories[$section]['repositories'][$repository] = 0; } } $contributors[$key]['repositories'] = []; + $contributors[$key]['repositoriesByYear'] = []; } $companiesData = json_decode(\file_get_contents(self::FILE_DATA_COMPANIES), true); @@ -175,20 +179,32 @@ protected function execute(InputInterface $input, OutputInterface $output): int return in_array($repository, self::REPOSITORIES_CATEGORIES[$item]) ? $item : $carry; }, 'others'); - ++$contributors[$authorLogin]['mergedPullRequests']; + self::bumpPair( + $contributors[$authorLogin]['mergedPullRequests'], + $contributors[$authorLogin]['mergedPullRequestsByYear'], + $yearMerged + ); // Repositoies if (!array_key_exists($repository, $contributors[$authorLogin]['repositories'])) { $contributors[$authorLogin]['repositories'][$repository] = 0; + $contributors[$authorLogin]['repositoriesByYear'][$repository] = []; } - ++$contributors[$authorLogin]['repositories'][$repository]; + self::bumpPair( + $contributors[$authorLogin]['repositories'][$repository], + $contributors[$authorLogin]['repositoriesByYear'][$repository], + $yearMerged + ); // Section : Total - ++$contributors[$authorLogin]['categories'][$section]['total']; + $cat = &$contributors[$authorLogin]['categories'][$section]; + self::bumpPair($cat['total'], $cat['byYear'], $yearMerged); // Section : Repositories - if (!array_key_exists($repository, $contributors[$authorLogin]['categories'][$section]['repositories'])) { - $contributors[$authorLogin]['categories'][$section]['repositories'][$repository] = 0; + if (!array_key_exists($repository, $cat['repositories'])) { + $cat['repositories'][$repository] = 0; + $cat['repositoriesByYear'][$repository] = []; } - ++$contributors[$authorLogin]['categories'][$section]['repositories'][$repository]; + self::bumpPair($cat['repositories'][$repository], $cat['repositoriesByYear'][$repository], $yearMerged); + unset($cat); } $company = $this->extractCompany($pullRequestData); @@ -197,6 +213,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int } else { $objCompany = $community; } + if ($company && $authorLogin !== '') { + $company->contributors[] = $authorLogin; + } // Company : Total PRs ++$objCompany->mergedPullRequests; // Company; Total contributions @@ -583,4 +602,19 @@ protected function writeFileContributorsPRs(array $contributors): void }); \file_put_contents(self::FILE_CONTRIBUTORS_PRS, json_encode($contributors, JSON_PRETTY_PRINT)); } + + /** + * Increments a scalar counter AND its parallel year map (additive-strict pattern). + * + * @param array $byYear + */ + private static function bumpPair(int &$scalar, array &$byYear, string $year): void + { + $scalar++; + if (!isset($byYear[$year])) { + $byYear[$year] = 0; + krsort($byYear); + } + $byYear[$year]++; + } } From af4a7f77ba4668b2b3681e8dc62b3343c9d0e62f Mon Sep 17 00:00:00 2001 From: Jonathan Danse Date: Thu, 9 Jul 2026 09:34:14 +0200 Subject: [PATCH 3/6] feat(topstats): add byYear sibling maps on reviews/issues/PRs, plus updatedAt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GenerateTopStatsCommand: aggregate() now tracks reviewsByYear, issuesOpenedByYear, pullRequestsOpenedByYear alongside the existing scalar counters (additive-strict, bumpPair() helper). - buildRanking() items gain a countByYear sibling next to count; top_reviewers/top_issues/top_pullrequests.json now wrap items under {updatedAt (ISO 8601 / DATE_ATOM), items}. - contributors_prs.json enrichment gains reviewsByYear/issuesOpenedByYear/ pullRequestsOpenedByYear siblings next to the existing scalars. - FetchPullRequestsAllCommand / FetchIssuesCommand: added createdAt (PR and issue) and submittedAt (review) to the GraphQL queries and output shape — required because the per-year breakdown has no date to bucket on otherwise. Purely additive new fields, no existing field touched. Co-Authored-By: Claude Opus 4.7 --- src/Command/FetchIssuesCommand.php | 2 + src/Command/FetchPullRequestsAllCommand.php | 4 + src/Command/GenerateTopStatsCommand.php | 88 +++++++++++++++++---- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/Command/FetchIssuesCommand.php b/src/Command/FetchIssuesCommand.php index fe0215d..fb3dea5 100644 --- a/src/Command/FetchIssuesCommand.php +++ b/src/Command/FetchIssuesCommand.php @@ -71,6 +71,7 @@ protected function fetchOrgIssues(): void } nodes { number + createdAt author { login avatarUrl @@ -104,6 +105,7 @@ protected function fetchOrgIssues(): void 'avatar_url' => $author['avatarUrl'] ?? null, 'html_url' => $author['url'] ?? null, 'repository' => $node['repository']['name'], + 'createdAt' => $node['createdAt'] ?? null, ]; } diff --git a/src/Command/FetchPullRequestsAllCommand.php b/src/Command/FetchPullRequestsAllCommand.php index 896237a..c62ff86 100644 --- a/src/Command/FetchPullRequestsAllCommand.php +++ b/src/Command/FetchPullRequestsAllCommand.php @@ -71,6 +71,7 @@ protected function fetchOrgPullRequests(): void } nodes { number + createdAt author { login avatarUrl @@ -81,6 +82,7 @@ protected function fetchOrgPullRequests(): void } reviews(first: 100) { nodes { + submittedAt author { login avatarUrl @@ -115,6 +117,7 @@ protected function fetchOrgPullRequests(): void 'name' => $reviewer['name'] ?? null, 'avatar_url' => $reviewer['avatarUrl'] ?? null, 'html_url' => $reviewer['url'] ?? null, + 'submittedAt' => $review['submittedAt'] ?? null, ]; } } @@ -125,6 +128,7 @@ protected function fetchOrgPullRequests(): void 'name' => $author['name'] ?? null, 'avatar_url' => $author['avatarUrl'] ?? null, 'html_url' => $author['url'] ?? null, + 'createdAt' => $node['createdAt'] ?? null, 'reviewers' => array_values($reviewers), ]; } diff --git a/src/Command/GenerateTopStatsCommand.php b/src/Command/GenerateTopStatsCommand.php index 62e2118..4badb07 100644 --- a/src/Command/GenerateTopStatsCommand.php +++ b/src/Command/GenerateTopStatsCommand.php @@ -46,9 +46,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->fetchConfiguration($input->getOption('config')); - /** @var array}> $pullRequests */ + /** @var array}> $pullRequests */ $pullRequests = json_decode(file_get_contents(self::FILE_PULLREQUESTS_ALL) ?: '', true); - /** @var array $issues */ + /** @var array $issues */ $issues = json_decode(file_get_contents(self::FILE_ISSUES) ?: '', true); /** @var array $contributors */ $contributors = json_decode(file_get_contents(self::FILE_CONTRIBUTORS_PRS) ?: '', true); @@ -59,9 +59,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->enrichContributors($contributors, $aggregate); file_put_contents(self::FILE_CONTRIBUTORS_PRS, json_encode($contributors, JSON_PRETTY_PRINT)); - file_put_contents(self::FILE_TOP_REVIEWERS, json_encode($this->buildRanking($aggregate['reviews'], $contributors, $identities), JSON_PRETTY_PRINT)); - file_put_contents(self::FILE_TOP_ISSUES, json_encode($this->buildRanking($aggregate['issuesOpened'], $contributors, $identities), JSON_PRETTY_PRINT)); - file_put_contents(self::FILE_TOP_PULLREQUESTS, json_encode($this->buildRanking($aggregate['pullRequestsOpened'], $contributors, $identities), JSON_PRETTY_PRINT)); + file_put_contents(self::FILE_TOP_REVIEWERS, json_encode($this->buildRanking($aggregate['reviews'], $aggregate['reviewsByYear'], $contributors, $identities), JSON_PRETTY_PRINT)); + file_put_contents(self::FILE_TOP_ISSUES, json_encode($this->buildRanking($aggregate['issuesOpened'], $aggregate['issuesOpenedByYear'], $contributors, $identities), JSON_PRETTY_PRINT)); + file_put_contents(self::FILE_TOP_PULLREQUESTS, json_encode($this->buildRanking($aggregate['pullRequestsOpened'], $aggregate['pullRequestsOpenedByYear'], $contributors, $identities), JSON_PRETTY_PRINT)); $this->output->writeLn(['', 'Top stats generated.']); @@ -69,21 +69,63 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @param array}> $pullRequests - * @param array $issues + * Increments a scalar counter AND its parallel year map (additive-strict pattern). + * + * @param array $byYear + */ + private static function bumpPair(int &$scalar, array &$byYear, string $year): void + { + $scalar++; + if (!isset($byYear[$year])) { + $byYear[$year] = 0; + krsort($byYear); + } + $byYear[$year]++; + } + + /** + * Increments the scalar counter unconditionally, and bumps the year map only + * if $rawDate parses to a valid timestamp. An empty/malformed date must NOT + * fall back to "now" — that would silently misattribute old items to the + * current year and corrupt the byYear breakdown. Skipping the year bump + * (while still counting the scalar) keeps totals correct and honest. + * + * @param array $byYear + */ + private static function bumpPairFromDate(int &$scalar, array &$byYear, ?string $rawDate): void + { + $ts = ($rawDate !== null && $rawDate !== '') ? strtotime($rawDate) : false; + if ($ts === false) { + $scalar++; + + return; + } + self::bumpPair($scalar, $byYear, date('Y', $ts)); + } + + /** + * @param array}> $pullRequests + * @param array $issues * - * @return array{reviews: array, issuesOpened: array, pullRequestsOpened: array} + * @return array{reviews: array, reviewsByYear: array>, issuesOpened: array, issuesOpenedByYear: array>, pullRequestsOpened: array, pullRequestsOpenedByYear: array>} */ public function aggregate(array $pullRequests, array $issues): array { $reviews = []; + $reviewsByYear = []; $pullRequestsOpened = []; + $pullRequestsOpenedByYear = []; $issuesOpened = []; + $issuesOpenedByYear = []; foreach ($pullRequests as $pullRequest) { $author = $pullRequest['login'] ?? null; if ($author !== null) { - $pullRequestsOpened[$author] = ($pullRequestsOpened[$author] ?? 0) + 1; + if (!isset($pullRequestsOpened[$author])) { + $pullRequestsOpened[$author] = 0; + $pullRequestsOpenedByYear[$author] = []; + } + self::bumpPairFromDate($pullRequestsOpened[$author], $pullRequestsOpenedByYear[$author], $pullRequest['createdAt'] ?? null); } // A review counts for its reviewer regardless of the PR author (even a // deleted/null author) — only self-reviews are excluded. @@ -92,21 +134,32 @@ public function aggregate(array $pullRequests, array $issues): array if ($reviewerLogin === $author) { continue; } - $reviews[$reviewerLogin] = ($reviews[$reviewerLogin] ?? 0) + 1; + if (!isset($reviews[$reviewerLogin])) { + $reviews[$reviewerLogin] = 0; + $reviewsByYear[$reviewerLogin] = []; + } + self::bumpPairFromDate($reviews[$reviewerLogin], $reviewsByYear[$reviewerLogin], $reviewer['submittedAt'] ?? null); } } foreach ($issues as $issue) { $author = $issue['login'] ?? null; if ($author !== null) { - $issuesOpened[$author] = ($issuesOpened[$author] ?? 0) + 1; + if (!isset($issuesOpened[$author])) { + $issuesOpened[$author] = 0; + $issuesOpenedByYear[$author] = []; + } + self::bumpPairFromDate($issuesOpened[$author], $issuesOpenedByYear[$author], $issue['createdAt'] ?? null); } } return [ 'reviews' => $reviews, + 'reviewsByYear' => $reviewsByYear, 'issuesOpened' => $issuesOpened, + 'issuesOpenedByYear' => $issuesOpenedByYear, 'pullRequestsOpened' => $pullRequestsOpened, + 'pullRequestsOpenedByYear' => $pullRequestsOpenedByYear, ]; } @@ -145,7 +198,7 @@ public function buildIdentities(array $pullRequests, array $issues): array /** * @param array $contributors - * @param array{reviews: array, issuesOpened: array, pullRequestsOpened: array} $aggregate + * @param array{reviews: array, reviewsByYear: array>, issuesOpened: array, issuesOpenedByYear: array>, pullRequestsOpened: array, pullRequestsOpenedByYear: array>} $aggregate */ private function enrichContributors(array &$contributors, array $aggregate): void { @@ -154,20 +207,24 @@ private function enrichContributors(array &$contributors, array $aggregate): voi continue; } $entry['reviews'] = $aggregate['reviews'][$login] ?? 0; + $entry['reviewsByYear'] = $aggregate['reviewsByYear'][$login] ?? []; $entry['issuesOpened'] = $aggregate['issuesOpened'][$login] ?? 0; + $entry['issuesOpenedByYear'] = $aggregate['issuesOpenedByYear'][$login] ?? []; $entry['pullRequestsOpened'] = $aggregate['pullRequestsOpened'][$login] ?? 0; + $entry['pullRequestsOpenedByYear'] = $aggregate['pullRequestsOpenedByYear'][$login] ?? []; } unset($entry); } /** * @param array $counts + * @param array> $countsByYear * @param array $contributors * @param array $identities * - * @return array{updatedAt: string, items: array} + * @return array{updatedAt: string, items: array}>} */ - private function buildRanking(array $counts, array $contributors, array $identities): array + private function buildRanking(array $counts, array $countsByYear, array $contributors, array $identities): array { $logins = array_keys($counts); usort($logins, function (string $a, string $b) use ($counts): int { @@ -191,11 +248,12 @@ private function buildRanking(array $counts, array $contributors, array $identit 'avatar_url' => $identity['avatar_url'], 'html_url' => $identity['html_url'], 'count' => $counts[$login], + 'countByYear' => $countsByYear[$login] ?? [], ]; ++$rank; } - return ['updatedAt' => date('Y-m-d'), 'items' => $items]; + return ['updatedAt' => (new \DateTimeImmutable())->format(DATE_ATOM), 'items' => $items]; } /** From d9085ea4770db30899b9003cc1d3cc712e86fc83 Mon Sep 17 00:00:00 2001 From: Jonathan Danse Date: Thu, 9 Jul 2026 09:49:25 +0200 Subject: [PATCH 4/6] feat(topsecurity): add byYear sibling maps on research/remediation/count, plus updatedAt Wraps top_security.json's updatedAt in ISO 8601 (DATE_ATOM) instead of Y-m-d, and adds countByYear/researchByYear/remediationByYear sibling maps keyed by advisory published_at year, following the additive-strict bumpPairFromDate pattern from Task 3. Existing scalar count/research/ remediation keys are unchanged. A missing/malformed published_at still increments the scalar but is skipped from the year bucket, so it is never misattributed to the current year. --- src/Command/GenerateTopSecurityCommand.php | 67 +++++++++++++++++++--- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/src/Command/GenerateTopSecurityCommand.php b/src/Command/GenerateTopSecurityCommand.php index 0326fe5..be51ff9 100644 --- a/src/Command/GenerateTopSecurityCommand.php +++ b/src/Command/GenerateTopSecurityCommand.php @@ -45,7 +45,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->fetchConfiguration($input->getOption('config')); - /** @var array}> $advisories */ + /** @var array}> $advisories */ $advisories = json_decode(file_get_contents(self::FILE_SECURITY_ADVISORIES) ?: '', true); /** @var array $contributors */ $contributors = file_exists(self::FILE_CONTRIBUTORS_PRS) @@ -60,10 +60,45 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @param array}> $advisories + * Increments a scalar counter AND its parallel year map (additive-strict pattern). + * + * @param array $byYear + */ + private static function bumpPair(int &$scalar, array &$byYear, string $year): void + { + $scalar++; + if (!isset($byYear[$year])) { + $byYear[$year] = 0; + krsort($byYear); + } + $byYear[$year]++; + } + + /** + * Increments the scalar counter unconditionally, and bumps the year map only + * if $rawDate parses to a valid timestamp. An empty/malformed date must NOT + * fall back to "now" — that would silently misattribute old items to the + * current year and corrupt the byYear breakdown. Skipping the year bump + * (while still counting the scalar) keeps totals correct and honest. + * + * @param array $byYear + */ + private static function bumpPairFromDate(int &$scalar, array &$byYear, ?string $rawDate): void + { + $ts = ($rawDate !== null && $rawDate !== '') ? strtotime($rawDate) : false; + if ($ts === false) { + $scalar++; + + return; + } + self::bumpPair($scalar, $byYear, date('Y', $ts)); + } + + /** + * @param array}> $advisories * @param array $contributors * - * @return array{updatedAt: string, items: array} + * @return array{updatedAt: string, items: array, research:int, researchByYear:array, remediation:int, remediationByYear:array}>} */ public function buildRanking(array $advisories, array $contributors): array { @@ -73,6 +108,7 @@ public function buildRanking(array $advisories, array $contributors): array $counts = []; $identities = []; foreach ($advisories as $advisory) { + $publishedAt = $advisory['published_at'] ?? null; $perLogin = []; foreach ($advisory['credits'] as $credit) { $type = $credit['type'] ?? ''; @@ -92,9 +128,23 @@ public function buildRanking(array $advisories, array $contributors): array } } foreach ($perLogin as $login => $families) { - $counts[$login]['count'] = ($counts[$login]['count'] ?? 0) + 1; - $counts[$login]['research'] = ($counts[$login]['research'] ?? 0) + ($families['research'] ? 1 : 0); - $counts[$login]['remediation'] = ($counts[$login]['remediation'] ?? 0) + ($families['remediation'] ? 1 : 0); + if (!isset($counts[$login])) { + $counts[$login] = [ + 'count' => 0, + 'countByYear' => [], + 'research' => 0, + 'researchByYear' => [], + 'remediation' => 0, + 'remediationByYear' => [], + ]; + } + self::bumpPairFromDate($counts[$login]['count'], $counts[$login]['countByYear'], $publishedAt); + if ($families['research']) { + self::bumpPairFromDate($counts[$login]['research'], $counts[$login]['researchByYear'], $publishedAt); + } + if ($families['remediation']) { + self::bumpPairFromDate($counts[$login]['remediation'], $counts[$login]['remediationByYear'], $publishedAt); + } } } @@ -117,12 +167,15 @@ public function buildRanking(array $advisories, array $contributors): array 'avatar_url' => (string) ($contributor['avatar_url'] ?? $identities[$login]['avatar_url']), 'html_url' => (string) ($contributor['html_url'] ?? $identities[$login]['html_url']), 'count' => $counts[$login]['count'], + 'countByYear' => $counts[$login]['countByYear'], 'research' => $counts[$login]['research'], + 'researchByYear' => $counts[$login]['researchByYear'], 'remediation' => $counts[$login]['remediation'], + 'remediationByYear' => $counts[$login]['remediationByYear'], ]; ++$rank; } - return ['updatedAt' => date('Y-m-d'), 'items' => $items]; + return ['updatedAt' => (new \DateTimeImmutable())->format(DATE_ATOM), 'items' => $items]; } } From c660d826bbbde7cd9275bd2cc020571d7fdaaa1e Mon Sep 17 00:00:00 2001 From: Jonathan Danse Date: Thu, 9 Jul 2026 10:07:42 +0200 Subject: [PATCH 5/6] feat(topcompanies): add updatedAt timestamp on JSON outputs Co-Authored-By: Claude Opus 4.7 --- src/Command/GenerateTopCompaniesCommand.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Command/GenerateTopCompaniesCommand.php b/src/Command/GenerateTopCompaniesCommand.php index cfeceef..912913a 100644 --- a/src/Command/GenerateTopCompaniesCommand.php +++ b/src/Command/GenerateTopCompaniesCommand.php @@ -502,6 +502,7 @@ protected function writeFileTopCompaniesByPRs(array $rankedCompanies, Company $c } \file_put_contents(self::FILE_TOP_COMPANIES_PRS, json_encode([ + 'updatedAt' => (new DateTimeImmutable())->format(DATE_ATOM), 'community' => $community->toArray(false), 'companies' => array_map(function (Company $company) { return $company->toArray(false); @@ -600,6 +601,7 @@ protected function writeFileContributorsPRs(array $contributors): void return ($contributorA['mergedPullRequests'] > $contributorB['mergedPullRequests']) ? -1 : 1; }); + $contributors['updatedAt'] = (new DateTimeImmutable())->format(DATE_ATOM); \file_put_contents(self::FILE_CONTRIBUTORS_PRS, json_encode($contributors, JSON_PRETTY_PRINT)); } From 80fd17a1108ebe2c04dc136db200763c304117cb Mon Sep 17 00:00:00 2001 From: Jonathan Danse Date: Fri, 10 Jul 2026 14:33:50 +0200 Subject: [PATCH 6/6] style: apply php-cs-fixer conventions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pre-increment (`++$x`) instead of post-increment (`$x++`) when the value is discarded — matches the repo's PHP-CS-Fixer ruleset. - Import `DateTimeImmutable` via `use` at file scope rather than \ reaching into the root namespace with a fully-qualified name. CI failure surfaced by @Progi1984 on PrestaShop/traces#237. Co-Authored-By: Claude Opus 4.7 --- src/Command/GenerateTopCompaniesCommand.php | 4 ++-- src/Command/GenerateTopSecurityCommand.php | 9 +++++---- src/Command/GenerateTopStatsCommand.php | 9 +++++---- src/DTO/Company.php | 1 + 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Command/GenerateTopCompaniesCommand.php b/src/Command/GenerateTopCompaniesCommand.php index 912913a..40ef4a4 100644 --- a/src/Command/GenerateTopCompaniesCommand.php +++ b/src/Command/GenerateTopCompaniesCommand.php @@ -612,11 +612,11 @@ protected function writeFileContributorsPRs(array $contributors): void */ private static function bumpPair(int &$scalar, array &$byYear, string $year): void { - $scalar++; + ++$scalar; if (!isset($byYear[$year])) { $byYear[$year] = 0; krsort($byYear); } - $byYear[$year]++; + ++$byYear[$year]; } } diff --git a/src/Command/GenerateTopSecurityCommand.php b/src/Command/GenerateTopSecurityCommand.php index be51ff9..2094815 100644 --- a/src/Command/GenerateTopSecurityCommand.php +++ b/src/Command/GenerateTopSecurityCommand.php @@ -2,6 +2,7 @@ namespace PrestaShop\Traces\Command; +use DateTimeImmutable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -66,12 +67,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private static function bumpPair(int &$scalar, array &$byYear, string $year): void { - $scalar++; + ++$scalar; if (!isset($byYear[$year])) { $byYear[$year] = 0; krsort($byYear); } - $byYear[$year]++; + ++$byYear[$year]; } /** @@ -87,7 +88,7 @@ private static function bumpPairFromDate(int &$scalar, array &$byYear, ?string $ { $ts = ($rawDate !== null && $rawDate !== '') ? strtotime($rawDate) : false; if ($ts === false) { - $scalar++; + ++$scalar; return; } @@ -176,6 +177,6 @@ public function buildRanking(array $advisories, array $contributors): array ++$rank; } - return ['updatedAt' => (new \DateTimeImmutable())->format(DATE_ATOM), 'items' => $items]; + return ['updatedAt' => (new DateTimeImmutable())->format(DATE_ATOM), 'items' => $items]; } } diff --git a/src/Command/GenerateTopStatsCommand.php b/src/Command/GenerateTopStatsCommand.php index 4badb07..6f64efa 100644 --- a/src/Command/GenerateTopStatsCommand.php +++ b/src/Command/GenerateTopStatsCommand.php @@ -2,6 +2,7 @@ namespace PrestaShop\Traces\Command; +use DateTimeImmutable; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -75,12 +76,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private static function bumpPair(int &$scalar, array &$byYear, string $year): void { - $scalar++; + ++$scalar; if (!isset($byYear[$year])) { $byYear[$year] = 0; krsort($byYear); } - $byYear[$year]++; + ++$byYear[$year]; } /** @@ -96,7 +97,7 @@ private static function bumpPairFromDate(int &$scalar, array &$byYear, ?string $ { $ts = ($rawDate !== null && $rawDate !== '') ? strtotime($rawDate) : false; if ($ts === false) { - $scalar++; + ++$scalar; return; } @@ -253,7 +254,7 @@ private function buildRanking(array $counts, array $countsByYear, array $contrib ++$rank; } - return ['updatedAt' => (new \DateTimeImmutable())->format(DATE_ATOM), 'items' => $items]; + return ['updatedAt' => (new DateTimeImmutable())->format(DATE_ATOM), 'items' => $items]; } /** diff --git a/src/DTO/Company.php b/src/DTO/Company.php index f6b7bd0..9f8a61b 100644 --- a/src/DTO/Company.php +++ b/src/DTO/Company.php @@ -53,6 +53,7 @@ private static function slugify(string $value): string { $ascii = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value); $ascii = preg_replace('/[^A-Za-z0-9]+/', '-', $ascii ?: $value) ?? $value; + return strtolower(trim($ascii, '-')) ?: 'company'; }