Why I see Seiger\sTask\sTaskServiceProvider multiple times?
PS C:\projects\Opensource\evolution\core> php artisan package:discover
EvolutionCMS\evoAi\evoAiServiceProvider
Seiger\sApi\sApiServiceProvider
Seiger\sTask\sTaskServiceProvider
Seiger\sCommerce\sCommerceServiceProvider
Laravel\Ai\AiServiceProvider
Seiger\sTask\sTaskServiceProvider
Seiger\sGallery\sGalleryServiceProvider
Seiger\sTask\sTaskServiceProvider
Why: package:discover (core/src/Console/Packages/PackageCommand.php) makes two passes and does not deduplicate.
Pass 1 — parseComposer() walks core/custom/composer.json require in order (core/src/Console/Packages/PackageCommand.php:166):
- evolution-cms/eai → evoAiServiceProvider
- seiger/sapi → sApiServiceProvider
- seiger/stask → sTaskServiceProvider (1st)
- seiger/scommerce → sCommerceServiceProvider
While doing that, checkRequired() (:322) appends each package's own require keys into a flat $this->require array — duplicates included.
Pass 2 — loadRequire() (:332) replays that flat list:
- from eai: laravel/ai → AiServiceProvider, seiger/stask → sTaskServiceProvider (2nd)
- from scommerce: seiger/sgallery → sGalleryServiceProvider, seiger/stask → sTaskServiceProvider (3rd)
So sTask appears 3× because it's both a direct requirement and a transitive dep of two packages (eai and scommerce), and neither $this->require nor process() tracks what's already been handled.
Is it a problem? No. process() writes one file per class name — core/custom/config/app/providers/sTaskServiceProvider.php — so the 2nd and 3rd runs just overwrite identical content. The provider is registered once. It's
cosmetic noise plus redundant file writes.
(Side note found while tracing: loadRequire() doesn't recurse, so dependency discovery is only 2 levels deep — a provider from a dep-of-a-dep-of-a-dep wouldn't be found. Not your issue today, but it's the same code path.)
Deduplicate package:discover output
Context
Running php artisan package:discover prints Seiger\sTask\sTaskServiceProvider three
times. This is not a bug in registration — it is duplicated work and misleading output.
core/src/Console/Packages/PackageCommand.php discovers providers in two passes:
- parseComposer() iterates core/custom/composer.json → require and, for each package,
calls checkRequired() which appends that package's own require keys onto a flat
$this->require array (:322-330) with no duplicate check.
- loadRequire() (:332-341) then replays every entry of that flat array through
parseComposerServiceProvider().
seiger/stask is a direct requirement of core/custom/composer.json and a requirement of
both evolution-cms/eai and seiger/scommerce, so it is processed three times. Each pass
rewrites the same core/custom/config/app/providers/sTaskServiceProvider.php, so the final
state is correct — only the console output and file I/O are redundant.
Secondary observation: loadRequire() does not recurse, so provider discovery stops at
two levels of dependency depth. A provider declared by a dependency-of-a-dependency-of-a-
dependency is never discovered.
Intended outcome: each discovered provider is reported and written exactly once per run, and
(optionally) dependency traversal becomes depth-independent.
Change
Single file: core/src/Console/Packages/PackageCommand.php
- Add a protected array $processedPackages = [] guard. In parseComposer() and
loadRequire(), skip a package name already present, and record it before parsing.
This alone removes the duplicate lines.
- Add a protected array $processedProviders = [] guard inside process() so the same
provider class is never written/printed twice even if two packages declare it.
- Optional, only if the depth limitation should be fixed in the same change: replace the
checkRequired() + loadRequire() two-pass scheme with a single recursive walk driven
by the $processedPackages set — parse a package, emit its providers/aliases/files,
then recurse into its require entries. The visited-set makes recursion terminate on
the cyclic/diamond dependency graphs these packages form.
Keep parseComposerServiceProvider(), processAlias(), and cleanupAliases() behaviour
unchanged — alias cleanup already relies on $discoveredAliasFiles being populated for every
alias seen in a run, so the dedupe guard must sit at the package level, not skip alias
registration.
Verification
cd core
php artisan package:discover
Expect each of evoAiServiceProvider, sApiServiceProvider, sTaskServiceProvider,
sCommerceServiceProvider, AiServiceProvider, sGalleryServiceProvider exactly once.
Then confirm nothing was lost:
ls core/custom/config/app/providers/ # same 10 files as before the change
ls core/custom/config/app/aliases/ # aliases still present, none removed by cleanup
php artisan list # app boots; seiger/eai commands still registered
cd core && composer test
composer analyze # from repo root; error count must not increase
Why I see Seiger\sTask\sTaskServiceProvider multiple times?
Why: package:discover (core/src/Console/Packages/PackageCommand.php) makes two passes and does not deduplicate.
Pass 1 — parseComposer() walks core/custom/composer.json require in order (core/src/Console/Packages/PackageCommand.php:166):
While doing that, checkRequired() (:322) appends each package's own require keys into a flat $this->require array — duplicates included.
Pass 2 — loadRequire() (:332) replays that flat list:
So sTask appears 3× because it's both a direct requirement and a transitive dep of two packages (eai and scommerce), and neither $this->require nor process() tracks what's already been handled.
Is it a problem? No. process() writes one file per class name — core/custom/config/app/providers/sTaskServiceProvider.php — so the 2nd and 3rd runs just overwrite identical content. The provider is registered once. It's
cosmetic noise plus redundant file writes.
(Side note found while tracing: loadRequire() doesn't recurse, so dependency discovery is only 2 levels deep — a provider from a dep-of-a-dep-of-a-dep wouldn't be found. Not your issue today, but it's the same code path.)
Deduplicate package:discover output
Context
Running php artisan package:discover prints Seiger\sTask\sTaskServiceProvider three
times. This is not a bug in registration — it is duplicated work and misleading output.
core/src/Console/Packages/PackageCommand.php discovers providers in two passes:
calls checkRequired() which appends that package's own require keys onto a flat
$this->require array (:322-330) with no duplicate check.
parseComposerServiceProvider().
seiger/stask is a direct requirement of core/custom/composer.json and a requirement of
both evolution-cms/eai and seiger/scommerce, so it is processed three times. Each pass
rewrites the same core/custom/config/app/providers/sTaskServiceProvider.php, so the final
state is correct — only the console output and file I/O are redundant.
Secondary observation: loadRequire() does not recurse, so provider discovery stops at
two levels of dependency depth. A provider declared by a dependency-of-a-dependency-of-a-
dependency is never discovered.
Intended outcome: each discovered provider is reported and written exactly once per run, and
(optionally) dependency traversal becomes depth-independent.
Change
Single file: core/src/Console/Packages/PackageCommand.php
loadRequire(), skip a package name already present, and record it before parsing.
This alone removes the duplicate lines.
provider class is never written/printed twice even if two packages declare it.
checkRequired() + loadRequire() two-pass scheme with a single recursive walk driven
by the $processedPackages set — parse a package, emit its providers/aliases/files,
then recurse into its require entries. The visited-set makes recursion terminate on
the cyclic/diamond dependency graphs these packages form.
Keep parseComposerServiceProvider(), processAlias(), and cleanupAliases() behaviour
unchanged — alias cleanup already relies on $discoveredAliasFiles being populated for every
alias seen in a run, so the dedupe guard must sit at the package level, not skip alias
registration.
Verification
cd core
php artisan package:discover
Expect each of evoAiServiceProvider, sApiServiceProvider, sTaskServiceProvider,
sCommerceServiceProvider, AiServiceProvider, sGalleryServiceProvider exactly once.
Then confirm nothing was lost:
ls core/custom/config/app/providers/ # same 10 files as before the change
ls core/custom/config/app/aliases/ # aliases still present, none removed by cleanup
php artisan list # app boots; seiger/eai commands still registered
cd core && composer test
composer analyze # from repo root; error count must not increase