Skip to content

Duplication of required packages in package:discover output #2399

Description

@elcreator

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:

  1. 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.
  2. 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

  1. 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.
  2. Add a protected array $processedProviders = [] guard inside process() so the same
    provider class is never written/printed twice even if two packages declare it.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions