Skip to content

refactor(core)!: decouple discovery#2041

Open
brendt wants to merge 43 commits into3.xfrom
discovery-improvements
Open

refactor(core)!: decouple discovery#2041
brendt wants to merge 43 commits into3.xfrom
discovery-improvements

Conversation

@brendt
Copy link
Member

@brendt brendt commented Mar 11, 2026

Upgrade Guide

Install Rector if you haven't yet:

composer require rector/rector --dev 
vendor/bin/rector

Next, update Tempest; it's important to add the --no-scripts flag to prevent any errors from being thrown during the update.

composer require tempest/framework:^3.4 --no-scripts

Then configure Rector to upgrade to Tempest 3.4:

// rector.php

use \Tempest\Upgrade\Set\TempestSetList;

return RectorConfig::configure()
    // …
    ->withSets([TempestSetList::TEMPEST_34]);

Next, run Rector:

vendor/bin/rector

Finally: make sure all discovery caches are regenerated:

./tempest discovery:generate

Breaking changes:

  • Tempest\Core\DiscoveryCache moved to Tempest\Discovery\DiscoveryCache
  • Tempest\Core\DiscoveryCacheStrategy moved to Tempest\Discovery\DiscoveryCacheStrategy
  • Tempest\Core\Composer moved to Tempest\Discovery\Composer
  • Tempest\Core\ComposerJsonCouldNotBeLocated moved to Tempest\Discovery\ComposerJsonCouldNotBeLocated
  • Tempest\Core\DiscoveryCachingStrategyWasChanged moved to Tempest\Discovery\DiscoveryCachingStrategyWasChanged
  • Tempest\Core\DiscoveryConfig moved to Tempest\Discovery\DiscoveryConfig
  • Removed Kernel::$discoveryLocations and Kernel::$discoveryClasses property hooks
  • Removed FrameworkKernel::$discoveryLocations and FrameworkKernel::$discoveryClasses properties

Non-breaking changes:

  • (internal class) Tempest\Core\LoadDiscoveryClasses moved to Tempest\Discovery\BootDiscovery
  • (internal class) Tempest\Core\LoadDiscoveryLocations moved to Tempest\Discovery\AutoloadDiscoveryLocations
  • (internal class) Tempest\Core\DiscoveryCacheInitializer moved to Tempest\Discovery\DiscoveryCacheInitializer
  • (internal class) Tempest\Core\DiscoveryDiscovery moved to Tempest\Discovery\DiscoveryDiscovery

Discovery as a standalone package

tempest/discovery can be used as a standalone package in any application. All it needs is a PSR-11 compliant container.

Start by requiring tempest/discovery:

composer require tempest/discovery

Next, you can boot discovery:

use Tempest\Discovery\BootDiscovery;
use Tempest\Discovery\DiscoveryConfig;

// $container is any PSR-11 compliant container, already available in your app

new BootDiscovery(
    container: $container,
    config: DiscoveryConfig::autoload(__DIR__),
)();

Whenever this action is run, discovery will find all discovery classes, and run them against all registered locations.

Manually specify discovery locations

DiscoveryConfig::autoload() will scan a given root path and autmatically determine discovery locations by analyzing the composer.json file in that path. If you prefer another way of defining locations to scan, you can manually provide them via DiscoveryConfig:

use Tempest\Discovery\DiscoveryConfig;
use Tempest\Discovery\DiscoveryLocation;

$config = new DiscoveryConfig(locations: [
    new DiscoveryLocation('App\\', 'src/'),
    // …
]);

Config and caching

You can pass config and cache parameters into the BootDiscovery action, with these you can exclude files and classes from discovery, as well as config caching behavior:

use Tempest\Discovery\BootDiscovery;
use Tempest\Discovery\DiscoveryCache;
use Tempest\Discovery\DiscoveryCacheStrategy;
use Tempest\Discovery\DiscoveryConfig;

new BootDiscovery(
    container: $container,
    config: DiscoveryConfig::autoload(__DIR__)
        ->skipClasses(
            \App\Foo::class,
            \Tempest\Container\AutowireDiscovery::class
        )
        ->skipPaths(
            __DIR__ . '/../vendor/tempest/support'
        ),
    cache: new DiscoveryCache(
        strategy: DiscoveryCacheStrategy::PARTIAL,
        pool: new PhpFilesAdapter(
            directory: __DIR__ . '/.cache/discovery'
        )
    ),
)();

@brendt brendt requested a review from aidan-casey as a code owner March 11, 2026 10:10
@brendt brendt marked this pull request as draft March 11, 2026 10:10
@github-actions
Copy link

github-actions bot commented Mar 11, 2026

Benchmark Results

Comparison of discovery-improvements against 3.x (ab8cfb14398f19936e9c4be649a2c42c805dbcf1).

Open to see the benchmark results
Benchmark Set Mem. Peak Time Variability
ContainerBench(benchRegisterSingletonInstance) - 3.871mb +39.90% 1.251μs +44.40% ±1.19% -18.23%
ContainerBench(benchRegisterDefinition) - 3.871mb +57.22% 1.427μs +58.91% ±1.56% +12.07%
ContainerBench(benchRegisterInitializer) - 3.906mb +48.34% 4.222μs +13.03% ±1.20% -2.82%
ContainerBench(benchRegisterDynamicInitializer) - 3.907mb +43.80% 1.932μs +30.21% ±0.98% -18.21%
ContainerBench(benchRegisterClosureSingleton) - 3.871mb +48.87% 1.264μs +35.08% ±1.50% +36.52%
DiscoveryScanBench(benchFullDiscoveryScan) - 23.356mb +0.17% 14.867ms -71.59% ±0.53% +9.71%
DiscoveryScanBench(benchSinglePackageScan) clock (small) 23.357mb +0.17% 86.886μs -95.52% ±1.16% +85.84%
DiscoveryScanBench(benchSinglePackageScan) console (large) 23.357mb +0.17% 1.540ms -76.30% ±0.43% +27.52%
DiscoveryScanBench(benchSinglePackageScan) router (large) 23.357mb +0.17% 1.049ms -86.13% ±0.74% +37.72%

Generated by phpbench against commit 72fe86a

@iamdadmin
Copy link
Contributor

Aidan's not the only one who loves the commits :D

@brendt brendt changed the title refactor(core): decouple discovery refactor(core)!: decouple discovery Mar 12, 2026
@brendt brendt marked this pull request as ready for review March 12, 2026 09:32
@brendt brendt requested a review from innocenzi as a code owner March 12, 2026 09:32
@brendt brendt marked this pull request as draft March 12, 2026 09:34
@brendt
Copy link
Member Author

brendt commented Mar 12, 2026

Todo: add Rectors

@brendt brendt marked this pull request as ready for review March 12, 2026 10:16
@brendt brendt requested a review from xHeaven March 12, 2026 13:49
Comment on lines +44 to +46
// Transform $kernel->discoveryLocations to $kernel->registry->locations
return new PropertyFetch(
new PropertyFetch($node->var, 'registry'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Transform $kernel->discoveryLocations to $kernel->registry->locations
return new PropertyFetch(
new PropertyFetch($node->var, 'registry'),
// Transform $kernel->discoveryLocations to $kernel->discoveryConfig->locations
return new PropertyFetch(
new PropertyFetch($node->var, 'discoveryConfig'),

{
$this->rector
->runFixture(__DIR__ . '/Fixtures/KernelDiscoveryLocations.input.php')
->assertContains('$this->kernel->registry->locations')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
->assertContains('$this->kernel->registry->locations')
->assertContains('$this->kernel->discoveryConfig->locations')

{
$this->rector
->runFixture(__DIR__ . '/Fixtures/KernelDiscoveryClasses.input.php')
->assertContains('$this->kernel->registry->classes')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
->assertContains('$this->kernel->registry->classes')
->assertContains('$this->kernel->discoveryConfig->classes')

private function resolveDiscovery(string $discoveryClass): Discovery
{
/** @var Discovery $discovery */
$discovery = $this->container->get($discoveryClass);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could do a check with $this->container->has($discoveryClass) and instantiate a new $discoveryClass if the check returns with false. This would add support for containers with no autowiring.

Comment on lines 30 to 31
public bool $discoveryCache;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public bool $discoveryCache;

Dead code.

Comment on lines +16 to +17
'Tempest\Core\DiscoveryConfig' => 'Tempest\Discovery\DiscoveryConfig',
'Tempest\Core\CouldNotStoreDiscoveryCache' => 'Tempest\Discovery\CouldNotStoreDiscoveryCache',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Tempest\Core\DiscoveryConfig' => 'Tempest\Discovery\DiscoveryConfig',
'Tempest\Core\CouldNotStoreDiscoveryCache' => 'Tempest\Discovery\CouldNotStoreDiscoveryCache',
'Tempest\Core\DiscoveryConfig' => 'Tempest\Discovery\DiscoveryConfig',
'Tempest\Core\CouldNotStoreDiscoveryCache' => 'Tempest\Discovery\CouldNotStoreDiscoveryCache',
'Tempest\Core\DiscoveryCacheInitializer' => 'Tempest\Discovery\DiscoveryCacheInitializer',
'Tempest\Core\DiscoveryDiscovery' => 'Tempest\Discovery\DiscoveryDiscovery',

@@ -78,10 +79,11 @@ public function resolveKernel(): Kernel
{
$container = new GenericContainer();
$container->singleton(Container::class, $container);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$container->singleton(Container::class, $container);

new GenericContainer(); already registers Container::class (and a couple others) as singletons.


### Manually specify discovery locations

`DiscoveryConfig::autoload()` will scan a given root path and autmatically determine discovery locations by analyzing the composer.json file in that path. If you prefer another way of defining locations to scan, you can manually provide them via `DiscoveryConfig`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`DiscoveryConfig::autoload()` will scan a given root path and autmatically determine discovery locations by analyzing the composer.json file in that path. If you prefer another way of defining locations to scan, you can manually provide them via `DiscoveryConfig`:
`DiscoveryConfig::autoload()` will scan a given root path and automatically determine discovery locations by analyzing the composer.json file in that path. If you prefer another way of defining locations to scan, you can manually provide them via `DiscoveryConfig`:

Comment on lines 45 to 48
public array $discoveryLocations;

public array $discoveryClasses;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discoveryLocations and discoveryClasses got removed from Kernel, dead code.

Comment on lines +55 to +56
$this->discoveryLocations = $kernel->discoveryConfig->locations;
$this->discoveryClasses = $kernel->discoveryConfig->classes;
Copy link
Member

@xHeaven xHeaven Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$this->discoveryLocations = $kernel->discoveryConfig->locations;
$this->discoveryClasses = $kernel->discoveryConfig->classes;

See #2041 (comment)

Comment on lines 5 to 7
use Tempest\Container\Container;
use Tempest\Container\Initializer;
use Tempest\Container\Singleton;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still depend on container here, is that okay? container is only pulled in by require-dev.

@xHeaven
Copy link
Member

xHeaven commented Mar 12, 2026

There are also a couple Tempest\Core mentions regarding discovery in docs/1-essentials/05-discovery.md that are stale as the classes moved under Tempest\Discovery.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants