Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
69a7c7b
Add registry
brendt Mar 11, 2026
086545f
Move things around, decouple things, make Aidan happy.
brendt Mar 11, 2026
e1c00bf
QA
brendt Mar 11, 2026
ad30d43
QA
brendt Mar 11, 2026
e17108b
Moving classes a-round, oh yeah, a-round.
brendt Mar 11, 2026
fccc5c0
Aidan's gonna love reading these commit messages
brendt Mar 11, 2026
8162e91
Making my code uglier, to make Aidan's code work.
brendt Mar 11, 2026
1eb5cff
The atrocity is real
brendt Mar 11, 2026
2e1ca4c
Even cleanup can't save us…
brendt Mar 11, 2026
c76c35f
The horror…
brendt Mar 11, 2026
d50348e
Cleaning up, as far as possible
brendt Mar 11, 2026
57b2c2a
Wip. Yes, it's wip
brendt Mar 11, 2026
e5e319b
Moarrr WIP — but actually getting somewhere. Aidan's gonna be so happy
brendt Mar 11, 2026
66c4a77
Forgot to ran QA. Always a good idea
brendt Mar 11, 2026
8ae0a81
Did we actually… make it green?
brendt Mar 11, 2026
6410671
RectOOOOOOOOOOOOOAAAAAArrr
brendt Mar 11, 2026
7852936
We did not make it green :(
brendt Mar 11, 2026
dda1183
Ok now it'll be green!
brendt Mar 11, 2026
f616bdc
No but now for real
brendt Mar 11, 2026
1d62fc6
Improvement, yes?
brendt Mar 12, 2026
45b46b3
Update docs
brendt Mar 12, 2026
5876ebb
Remove container dependency from support
brendt Mar 12, 2026
029fdf1
Update docs
brendt Mar 12, 2026
00b9ca9
Fix deps
brendt Mar 12, 2026
a7ab796
Making it faster, better, stronger
brendt Mar 12, 2026
223fb13
Docs
brendt Mar 12, 2026
ba94673
Docs, of course
brendt Mar 12, 2026
d991965
QA, of course
brendt Mar 12, 2026
a606c0f
Testing
brendt Mar 12, 2026
1b7b3d3
Fixing testing
brendt Mar 12, 2026
d1f28b1
Fixing testing
brendt Mar 12, 2026
5ff8c02
QA
brendt Mar 12, 2026
1880b21
QA
brendt Mar 12, 2026
c283ecf
Rector wip
brendt Mar 12, 2026
d49074c
Rector wip
brendt Mar 12, 2026
47c0b6e
Fix cache path
brendt Mar 12, 2026
317215b
wip
brendt Mar 12, 2026
7a3a7b9
Fix namespace
brendt Mar 12, 2026
dd9d8d5
Remove registry
brendt Mar 12, 2026
16ffd0c
WIP
brendt Mar 12, 2026
0bf1bfe
WIP
brendt Mar 12, 2026
87f754f
WIP
brendt Mar 12, 2026
72fe86a
wip
brendt Mar 12, 2026
056f250
Update packages/upgrade/src/Tempest34/UpdateKernelDiscoveryProperties…
brendt Mar 13, 2026
aff9653
Update packages/upgrade/tests/Tempest34/Tempest34RectorTest.php
brendt Mar 13, 2026
b2aaa5a
Update packages/upgrade/tests/Tempest34/Tempest34RectorTest.php
brendt Mar 13, 2026
a751023
Apply suggestion from @xHeaven
brendt Mar 13, 2026
c4a2fc1
Apply suggestion from @xHeaven
brendt Mar 13, 2026
9dfa641
Apply suggestion from @xHeaven
brendt Mar 13, 2026
4e874fe
Support non-autowire containers
brendt Mar 13, 2026
3f1a001
Apply suggestion from @xHeaven
brendt Mar 13, 2026
4a95d9c
Cleanup
brendt Mar 13, 2026
e95af46
Merge remote-tracking branch 'origin/discovery-improvements' into dis…
brendt Mar 13, 2026
a6070f2
Cleanup
brendt Mar 13, 2026
a6978bb
wip
brendt Mar 13, 2026
1996f6b
wip
brendt Mar 13, 2026
d4a7fb1
wip
brendt Mar 13, 2026
5bfc2a7
wip
brendt Mar 13, 2026
1bf5dfe
wip
brendt Mar 13, 2026
8686b6d
wip
brendt Mar 13, 2026
a94c296
wip
brendt Mar 13, 2026
2888faf
Merge branch '3.x' into discovery-improvements
brendt Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"psr-discovery/http-factory-implementations": "^1.2",
"psr/cache": "^3.0",
"psr/clock": "^1.0.0",
"psr/container": "^2.0",
"psr/http-client": "^1.0.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0|^2.0",
Expand Down Expand Up @@ -73,6 +74,7 @@
"nesbot/carbon": "^3.8",
"nyholm/psr7": "^1.8",
"patrickbussmann/oauth2-apple": "^0.3",
"php-di/php-di": "^7.0",
"phpat/phpat": "^0.11.0",
"phpbench/phpbench": "^1.4",
"phpstan/phpstan": "2.1.40",
Expand Down Expand Up @@ -217,6 +219,7 @@
"Tempest\\Database\\Tests\\": "packages/database/tests",
"Tempest\\DateTime\\Tests\\": "packages/datetime/tests",
"Tempest\\Debug\\Tests\\": "packages/debug/tests",
"Tempest\\Discovery\\Tests\\": "packages/discovery/tests",
"Tempest\\EventBus\\Tests\\": "packages/event-bus/tests",
"Tempest\\Generation\\Tests\\": "packages/generation/tests",
"Tempest\\HttpClient\\Tests\\": "packages/http-client/tests",
Expand Down
93 changes: 93 additions & 0 deletions docs/1-essentials/05-discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,96 @@ Most of Tempest's features are built on top of discovery. The following is a non
- {b`Tempest\Vite\ViteDiscovery`} discovers `*.entrypoint.{ts,js,css}` files and register them as [entrypoints](../2-features/02-asset-bundling.md#entrypoints).
- {b`Tempest\Auth\AccessControl\PolicyDiscovery`} discovers methods annotated with the {b`#[Tempest\Auth\AccessControl\Policy]`} attribute and registers them as [access control policies](../2-features/04-authentication.md#access-control).
- {b`Tempest\Core\InsightsProviderDiscovery`} discovers classes that implement {b`Tempest\Core\InsightsProvider`} and registers them as insights providers, which power the `tempest about` command.

## 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`:

```console
composer require tempest/discovery
```

Next, you can boot discovery:

```php
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 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`:

```php
use Tempest\Discovery\DiscoveryConfig;
use Tempest\Discovery\DiscoveryLocation;

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

### Config

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:

```php
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'
)
),
)();
```

### Generating and clearing Discovery cache

By default, discovery cache will be set to `partial`, meaning that all vendor locations will be cached. Discovery cache needs to be generated before it can be used, though. If you're using `tempest/discovery` as a standalone package, you'll have to take care of generating this cache yourself. In Tempest, this is done with a `discovery:generate` CLI command, but you're free to implement it in any other way you seem fit.

Actually generating the cache can be with the {b`\Tempest\Discovery\GenerateDiscoveryCache`} action:

```php
use Tempest\Discovery\GenerateDiscoveryCache;
use Tempest\Discovery\DiscoveryConfig;

($this->generateDiscoveryCache)(
container: new Container(), // Pass in a clean container
config: $discoveryConfig, // You probably already have a configured `DiscoveryConfig` from setting up discovery
cache: $discoveryCache->withStrategy($strategy), // Make sure to set the strategy which you want to use for caching
);
```

It's important to note that discovery cache only works if the strategy used during generation is the same as subsequent requests. It's advised to always run cache generation code from within a script that doesn't have discovery cache enabled. For example:

```console
~ DISCOVERY_CACHE=false bin/console discovery:generate
~ DISCOVERY_CACHE=false artisan discovery:generate
```
2 changes: 1 addition & 1 deletion packages/cache/src/Commands/CacheClearCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use Tempest\Container\Container;
use Tempest\Container\GenericContainer;
use Tempest\Core\ConfigCache;
use Tempest\Core\DiscoveryCache;
use Tempest\Discovery\DiscoveryCache;
use Tempest\Icon\IconCache;
use Tempest\Support\Str;
use Tempest\View\ViewCache;
Expand Down
2 changes: 1 addition & 1 deletion packages/cache/src/Commands/CacheStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
use Tempest\Container\Container;
use Tempest\Container\GenericContainer;
use Tempest\Core\ConfigCache;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\Environment;
use Tempest\Discovery\DiscoveryCache;
use Tempest\Icon\IconCache;
use Tempest\Support\Str;
use Tempest\View\ViewCache;
Expand Down
4 changes: 2 additions & 2 deletions packages/cache/src/InternalCacheInsightsProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
namespace Tempest\Cache;

use Tempest\Core\ConfigCache;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\DiscoveryCacheStrategy;
use Tempest\Core\Insight;
use Tempest\Core\InsightsProvider;
use Tempest\Core\InsightType;
use Tempest\Discovery\DiscoveryCache;
use Tempest\Discovery\DiscoveryCacheStrategy;
use Tempest\Icon\IconCache;
use Tempest\View\ViewCache;

Expand Down
2 changes: 1 addition & 1 deletion packages/console/src/Middleware/OverviewMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
use Tempest\Console\ExitCode;
use Tempest\Console\Initializers\Invocation;
use Tempest\Core\AppConfig;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\Priority;
use Tempest\Discovery\DiscoveryCache;

use function Tempest\Support\arr;
use function Tempest\Support\str;
Expand Down
3 changes: 2 additions & 1 deletion packages/container/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"minimum-stability": "dev",
"require": {
"php": "^8.5",
"tempest/reflection": "3.x-dev"
"tempest/reflection": "3.x-dev",
"psr/container": "^2.0"
},
"autoload": {
"files": [
Expand Down
3 changes: 2 additions & 1 deletion packages/container/src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

namespace Tempest\Container;

use Psr\Container\ContainerInterface;
use Tempest\Reflection\ClassReflector;
use Tempest\Reflection\FunctionReflector;
use Tempest\Reflection\MethodReflector;
use UnitEnum;

interface Container
interface Container extends ContainerInterface
{
public function register(string $className, callable $definition): self;

Expand Down
10 changes: 9 additions & 1 deletion packages/container/src/GenericContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

use ArrayIterator;
use Closure;
use Psr\Container\ContainerInterface;
use ReflectionFunction;
use Tempest\Container\Exceptions\CircularDependencyEncountered;
use Tempest\Container\Exceptions\DecoratorDidNotImplementInterface;
use Tempest\Container\Exceptions\DependencyCouldNotBeAutowired;
use Tempest\Container\Exceptions\DependencyCouldNotBeInstantiated;
Expand Down Expand Up @@ -40,7 +42,11 @@ public function __construct(
/** @var ArrayIterator<array-key, class-string[]> $decorators */
private(set) ArrayIterator $decorators = new ArrayIterator(),
private(set) ?DependencyChain $chain = null,
) {}
) {
$this->singleton(Container::class, $this);
$this->singleton(ContainerInterface::class, $this);
$this->singleton(GenericContainer::class, $this);
}

public function setDefinitions(array $definitions): self
{
Expand Down Expand Up @@ -172,6 +178,8 @@ public function config(object $config): self
* @template TClassName of object
* @param class-string<TClassName> $className
* @return TClassName
* @throws CircularDependencyEncountered
* @throws TaggedDependencyCouldNotBeResolved
*/
public function get(string $className, null|string|UnitEnum $tag = null, mixed ...$params): object
{
Expand Down
11 changes: 7 additions & 4 deletions packages/core/src/Commands/DiscoveryClearCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@

namespace Tempest\Core\Commands;

use Tempest\Console\Console;
use Tempest\Console\ConsoleCommand;
use Tempest\Core\DiscoveryCache;
use Tempest\Console\HasConsole;
use Tempest\Discovery\ClearDiscoveryCache;
use Tempest\Discovery\DiscoveryCache;

if (class_exists(\Tempest\Console\ConsoleCommand::class)) {
final readonly class DiscoveryClearCommand
{
use HasConsole;

public function __construct(
private DiscoveryCache $discoveryCache,
private Console $console,
private ClearDiscoveryCache $clearDiscoveryCache,
) {}

#[ConsoleCommand(
Expand All @@ -25,7 +28,7 @@ public function __invoke(): void
{
$this->console->task(
label: 'Clearing discovery cache',
handler: fn () => $this->discoveryCache->clear(),
handler: fn () => ($this->clearDiscoveryCache)($this->discoveryCache),
);
}
}
Expand Down
67 changes: 26 additions & 41 deletions packages/core/src/Commands/DiscoveryGenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@

namespace Tempest\Core\Commands;

use Closure;
use Tempest\Console\ConsoleCommand;
use Tempest\Console\HasConsole;
use Tempest\Container\Container;
use Tempest\Container\GenericContainer;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\DiscoveryCacheStrategy;
use Tempest\Core\DiscoveryConfig;
use Tempest\Core\FrameworkKernel;
use Tempest\Core\Kernel;
use Tempest\Core\Kernel\LoadDiscoveryClasses;
use Tempest\Discovery\ClearDiscoveryCache;
use Tempest\Discovery\DiscoveryCache;
use Tempest\Discovery\DiscoveryCacheStrategy;
use Tempest\Discovery\DiscoveryConfig;
use Tempest\Discovery\GenerateDiscoveryCache;

if (class_exists(\Tempest\Console\ConsoleCommand::class)) {
final readonly class DiscoveryGenerateCommand
{
use HasConsole;

public function __construct(
private Kernel $kernel,
private FrameworkKernel $kernel,
private DiscoveryConfig $discoveryConfig,
private DiscoveryCache $discoveryCache,
private GenerateDiscoveryCache $generateDiscoveryCache,
private ClearDiscoveryCache $clearDiscoveryCache,
) {}

#[ConsoleCommand(
Expand All @@ -41,51 +42,35 @@ public function __invoke(): void
return;
}

$this->clearDiscoveryCache();

$this->console->task(
label: "Generating discovery cache using the `{$strategy->value}` strategy",
handler: fn (Closure $log) => $this->generateDiscoveryCache($strategy, $log),
label: 'Clearing discovery cache',
handler: fn () => ($this->clearDiscoveryCache)($this->discoveryCache),
);
}

public function clearDiscoveryCache(): void
{
$this->console->call(DiscoveryClearCommand::class);
}

public function generateDiscoveryCache(DiscoveryCacheStrategy $strategy, Closure $log): void
{
$kernel = $this->resolveKernel();

$loadDiscoveryClasses = new LoadDiscoveryClasses(
container: $kernel->container,
discoveryConfig: $kernel->container->get(DiscoveryConfig::class),
discoveryCache: $this->discoveryCache,
$this->console->task(
label: "Generating {$strategy->value} discovery cache",
handler: function () use ($strategy) {
$kernel = $this->resolveKernel();

($this->generateDiscoveryCache)(
container: $kernel->container,
config: $this->discoveryConfig,
cache: $this->discoveryCache->withStrategy($strategy),
);
},
);

$discoveries = $loadDiscoveryClasses->build();

foreach ($this->kernel->discoveryLocations as $location) {
$this->discoveryCache->store($location, $discoveries);
$log($location->path);
}

$this->discoveryCache->storeStrategy($strategy);
}

public function resolveKernel(): Kernel
private function resolveKernel(): FrameworkKernel
{
$container = new GenericContainer();
$container->singleton(Container::class, $container);

return new FrameworkKernel(
root: $this->kernel->root,
discoveryLocations: $this->kernel->discoveryLocations,
container: $container,
discoveryLocations: $this->kernel->discoveryConfig->locations,
container: new GenericContainer(),
)
->registerKernel()
->loadComposer()
->loadDiscoveryConfig()
->loadConfig();
}
}
Expand Down
Loading
Loading