diff --git a/composer.json b/composer.json index ce22082b6f..796dc8debb 100644 --- a/composer.json +++ b/composer.json @@ -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", @@ -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", @@ -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", diff --git a/docs/1-essentials/05-discovery.md b/docs/1-essentials/05-discovery.md index d6a4bc3e1e..77031b47a4 100644 --- a/docs/1-essentials/05-discovery.md +++ b/docs/1-essentials/05-discovery.md @@ -218,3 +218,72 @@ 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 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`: + +```php +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: + +```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' + ) + ), +)(); +``` \ No newline at end of file diff --git a/packages/cache/src/Commands/CacheClearCommand.php b/packages/cache/src/Commands/CacheClearCommand.php index fb1c7b13cb..84036ab77b 100644 --- a/packages/cache/src/Commands/CacheClearCommand.php +++ b/packages/cache/src/Commands/CacheClearCommand.php @@ -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; diff --git a/packages/cache/src/Commands/CacheStatusCommand.php b/packages/cache/src/Commands/CacheStatusCommand.php index 766eda0d27..7c2c70e189 100644 --- a/packages/cache/src/Commands/CacheStatusCommand.php +++ b/packages/cache/src/Commands/CacheStatusCommand.php @@ -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; diff --git a/packages/cache/src/InternalCacheInsightsProvider.php b/packages/cache/src/InternalCacheInsightsProvider.php index b1b0d039c9..754d5f8470 100644 --- a/packages/cache/src/InternalCacheInsightsProvider.php +++ b/packages/cache/src/InternalCacheInsightsProvider.php @@ -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; diff --git a/packages/console/src/Middleware/OverviewMiddleware.php b/packages/console/src/Middleware/OverviewMiddleware.php index f68ce82273..739ca7d45a 100644 --- a/packages/console/src/Middleware/OverviewMiddleware.php +++ b/packages/console/src/Middleware/OverviewMiddleware.php @@ -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; diff --git a/packages/container/composer.json b/packages/container/composer.json index 561630f19b..0d0473a7c7 100644 --- a/packages/container/composer.json +++ b/packages/container/composer.json @@ -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": [ diff --git a/packages/container/src/Container.php b/packages/container/src/Container.php index 8eec70c4f6..cacfe160bc 100644 --- a/packages/container/src/Container.php +++ b/packages/container/src/Container.php @@ -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; diff --git a/packages/container/src/GenericContainer.php b/packages/container/src/GenericContainer.php index 2483f0640b..7e57aa8ac7 100644 --- a/packages/container/src/GenericContainer.php +++ b/packages/container/src/GenericContainer.php @@ -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; @@ -40,7 +42,11 @@ public function __construct( /** @var ArrayIterator $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 { @@ -172,6 +178,8 @@ public function config(object $config): self * @template TClassName of object * @param class-string $className * @return TClassName + * @throws CircularDependencyEncountered + * @throws TaggedDependencyCouldNotBeResolved */ public function get(string $className, null|string|UnitEnum $tag = null, mixed ...$params): object { diff --git a/packages/core/src/Commands/DiscoveryClearCommand.php b/packages/core/src/Commands/DiscoveryClearCommand.php index b31776e113..2eb5acc6f7 100644 --- a/packages/core/src/Commands/DiscoveryClearCommand.php +++ b/packages/core/src/Commands/DiscoveryClearCommand.php @@ -6,7 +6,7 @@ use Tempest\Console\Console; use Tempest\Console\ConsoleCommand; -use Tempest\Core\DiscoveryCache; +use Tempest\Discovery\DiscoveryCache; if (class_exists(\Tempest\Console\ConsoleCommand::class)) { final readonly class DiscoveryClearCommand diff --git a/packages/core/src/Commands/DiscoveryGenerateCommand.php b/packages/core/src/Commands/DiscoveryGenerateCommand.php index ca286ddfa6..53f415f80b 100644 --- a/packages/core/src/Commands/DiscoveryGenerateCommand.php +++ b/packages/core/src/Commands/DiscoveryGenerateCommand.php @@ -9,12 +9,12 @@ 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\BootDiscovery; +use Tempest\Discovery\DiscoveryCache; +use Tempest\Discovery\DiscoveryCacheStrategy; +use Tempest\Discovery\DiscoveryConfig; if (class_exists(\Tempest\Console\ConsoleCommand::class)) { final readonly class DiscoveryGenerateCommand @@ -22,7 +22,8 @@ use HasConsole; public function __construct( - private Kernel $kernel, + private DiscoveryConfig $discoveryConfig, + private FrameworkKernel $kernel, private DiscoveryCache $discoveryCache, ) {} @@ -58,15 +59,15 @@ public function generateDiscoveryCache(DiscoveryCacheStrategy $strategy, Closure { $kernel = $this->resolveKernel(); - $loadDiscoveryClasses = new LoadDiscoveryClasses( + $bootDiscovery = new BootDiscovery( container: $kernel->container, - discoveryConfig: $kernel->container->get(DiscoveryConfig::class), - discoveryCache: $this->discoveryCache, + config: $kernel->container->get(DiscoveryConfig::class), + cache: $this->discoveryCache, ); - $discoveries = $loadDiscoveryClasses->build(); + $discoveries = $bootDiscovery->build(); - foreach ($this->kernel->discoveryLocations as $location) { + foreach ($this->discoveryConfig->locations as $location) { $this->discoveryCache->store($location, $discoveries); $log($location->path); } @@ -78,10 +79,11 @@ public function resolveKernel(): Kernel { $container = new GenericContainer(); $container->singleton(Container::class, $container); + $container->singleton(DiscoveryConfig::class, $this->discoveryConfig); return new FrameworkKernel( root: $this->kernel->root, - discoveryLocations: $this->kernel->discoveryLocations, + discoveryLocations: $this->kernel->discoveryConfig->locations, container: $container, ) ->registerKernel() diff --git a/packages/core/src/Commands/DiscoveryStatusCommand.php b/packages/core/src/Commands/DiscoveryStatusCommand.php index 1f037ff114..b8dadd7b53 100644 --- a/packages/core/src/Commands/DiscoveryStatusCommand.php +++ b/packages/core/src/Commands/DiscoveryStatusCommand.php @@ -7,9 +7,9 @@ use Tempest\Console\Console; use Tempest\Console\ConsoleArgument; use Tempest\Console\ConsoleCommand; -use Tempest\Core\DiscoveryCache; -use Tempest\Core\DiscoveryCacheStrategy; -use Tempest\Core\Kernel; +use Tempest\Discovery\DiscoveryCache; +use Tempest\Discovery\DiscoveryCacheStrategy; +use Tempest\Discovery\DiscoveryConfig; use Tempest\Support\Filesystem; use function Tempest\root_path; @@ -20,11 +20,11 @@ { public function __construct( private Console $console, - private Kernel $kernel, + private DiscoveryConfig $discoveryConfig, private DiscoveryCache $discoveryCache, ) {} - #[ConsoleCommand(name: 'discovery:status', description: 'Lists all discovery locations and discovery classes')] + #[ConsoleCommand(name: 'discovery:status', description: 'Lists all discovery locations and discovery classes', aliases: ['d:s'])] public function __invoke( #[ConsoleArgument(description: 'Prints discovery classes', aliases: ['c'])] bool $showClasses = false, @@ -32,8 +32,8 @@ public function __invoke( bool $showLocations = false, ): void { $this->console->header('Discovery status'); - $this->console->keyValue('Registered locations', (string) count($this->kernel->discoveryLocations)); - $this->console->keyValue('Loaded discovery classes', (string) count($this->kernel->discoveryClasses)); + $this->console->keyValue('Registered locations', (string) count($this->discoveryConfig->locations)); + $this->console->keyValue('Loaded discovery classes', (string) count($this->discoveryConfig->classes)); $this->console->keyValue('Cache', match ($this->discoveryCache->enabled) { true => 'ENABLED', false => 'DISABLED', @@ -53,7 +53,7 @@ public function __invoke( $this->console->header('Discovery classes', subheader: 'These classes are used by Tempest to determine which classes to discover and how to handle them.'); $this->console->writeln(); - foreach ($this->kernel->discoveryClasses as $discoveryClass) { + foreach ($this->discoveryConfig->classes as $discoveryClass) { $this->console->keyValue("{$discoveryClass}"); } } @@ -62,7 +62,7 @@ public function __invoke( $this->console->header('Discovery locations', subheader: 'These locations are used by Tempest to discover classes.'); $this->console->writeln(); - foreach ($this->kernel->discoveryLocations as $discoveryLocation) { + foreach ($this->discoveryConfig->locations as $discoveryLocation) { $path = str(Filesystem\normalize_path($discoveryLocation->path)) ->replaceStart(root_path(), '.') ->toString(); diff --git a/packages/core/src/FrameworkKernel.php b/packages/core/src/FrameworkKernel.php index 9fb47ddfeb..5f027a67a8 100644 --- a/packages/core/src/FrameworkKernel.php +++ b/packages/core/src/FrameworkKernel.php @@ -11,9 +11,14 @@ use Tempest\Container\GenericContainer; use Tempest\Core\Kernel\FinishDeferredTasks; use Tempest\Core\Kernel\LoadConfig; -use Tempest\Core\Kernel\LoadDiscoveryClasses; -use Tempest\Core\Kernel\LoadDiscoveryLocations; use Tempest\Core\Kernel\RegisterEmergencyExceptionHandler; +use Tempest\Discovery\AutoloadDiscoveryLocations; +use Tempest\Discovery\BootDiscovery; +use Tempest\Discovery\Composer; +use Tempest\Discovery\DiscoveryCache; +use Tempest\Discovery\DiscoveryCacheInitializer; +use Tempest\Discovery\DiscoveryConfig; +use Tempest\Discovery\DiscoveryLocation; use Tempest\EventBus\EventBus; use Tempest\Process\GenericProcessExecutor; use Tempest\Support\Filesystem; @@ -24,18 +29,22 @@ final class FrameworkKernel implements Kernel public bool $discoveryCache; - public array $discoveryClasses = []; - public string $internalStorage; + public DiscoveryConfig $discoveryConfig; + + /** @var DiscoveryLocation[] */ + private array $discoveryLocations; + public function __construct( public string $root, /** @var \Tempest\Discovery\DiscoveryLocation[] $discoveryLocations */ - public array $discoveryLocations = [], + array $discoveryLocations = [], ?Container $container = null, ?string $internalStorage = null, ) { $this->container = $container ?? $this->createContainer(); + $this->discoveryLocations = $discoveryLocations; if ($internalStorage !== null) { $this->internalStorage = $internalStorage; @@ -58,20 +67,29 @@ public static function boot( container: $container, internalStorage: $internalStorage, ) + ->registerKernel() ->validateRoot() ->loadEnv() ->registerEmergencyExceptionHandler() ->registerShutdownFunction() ->registerInternalStorage() - ->registerKernel() ->loadComposer() - ->loadDiscoveryLocations() + ->loadDiscoveryConfig() ->loadConfig() - ->loadDiscovery() + ->bootDiscovery() ->registerExceptionHandler() ->event(KernelEvent::BOOTED); } + public function createContainer(): GenericContainer + { + $container = new GenericContainer(); + + GenericContainer::setInstance($container); + + return $container; + } + public function validateRoot(): self { $root = Filesystem\normalize_path($this->root); @@ -93,17 +111,6 @@ public function shutdown(int|string $status = ''): never exit($status); } - public function createContainer(): Container - { - $container = new GenericContainer(); - - GenericContainer::setInstance($container); - - $container->singleton(Container::class, fn () => $container); - - return $container; - } - public function loadComposer(): self { if (class_exists(GenericProcessExecutor::class)) { @@ -156,17 +163,21 @@ public function registerShutdownFunction(): self return $this; } - public function loadDiscoveryLocations(): self + public function loadDiscoveryConfig(): self { - $this->container->invoke(LoadDiscoveryLocations::class); + /** @var DiscoveryConfig $discoveryConfig */ + $discoveryConfig = $this->container->get(DiscoveryConfig::class); - return $this; - } + $discoveryConfig->locations = [ + ...$this->discoveryLocations, + ...(new AutoloadDiscoveryLocations( + rootPath: $this->root, + composer: $this->container->get(Composer::class), + ))(), + ]; - public function loadDiscovery(): self - { - $this->container->addInitializer(DiscoveryCacheInitializer::class); - $this->container->invoke(LoadDiscoveryClasses::class, discoveryLocations: $this->discoveryLocations); + $this->container->config($discoveryConfig); + $this->discoveryConfig = $discoveryConfig; return $this; } @@ -175,15 +186,36 @@ public function loadConfig(): self { $this->container->addInitializer(ConfigCacheInitializer::class); - $loadConfig = $this->container->get(LoadConfig::class, environment: Environment::guessFromEnvironment()); + $loadConfig = new LoadConfig( + discoveryConfig: $this->container->get(DiscoveryConfig::class), + container: $this->container, + cache: $this->container->get(ConfigCache::class), + environment: Environment::guessFromEnvironment(), + ); + $loadConfig(); return $this; } + public function bootDiscovery(): self + { + $this->container->addInitializer(DiscoveryCacheInitializer::class); + + $bootDiscovery = new BootDiscovery( + container: $this->container, + config: $this->container->get(DiscoveryConfig::class), + cache: $this->container->get(DiscoveryCache::class), + ); + + $bootDiscovery(); + + return $this; + } + public function registerInternalStorage(): self { - $path = isset($this->internalStorage) ? $this->internalStorage : $this->root . '/.tempest'; + $path = $this->internalStorage ?? $this->root . '/.tempest'; if (! is_dir($path)) { if (file_exists($path)) { @@ -256,7 +288,7 @@ public function registerExceptionHandler(): self )); return true; - }, error_levels: E_ALL); + }); return $this; } diff --git a/packages/core/src/Kernel.php b/packages/core/src/Kernel.php index 65f45fb96d..416ff00afc 100644 --- a/packages/core/src/Kernel.php +++ b/packages/core/src/Kernel.php @@ -18,16 +18,6 @@ interface Kernel get; } - public array $discoveryLocations { - get; - set; - } - - public array $discoveryClasses { - get; - set; - } - public Container $container { get; } diff --git a/packages/core/src/Kernel/LoadConfig.php b/packages/core/src/Kernel/LoadConfig.php index 6c916f1664..1492f6803c 100644 --- a/packages/core/src/Kernel/LoadConfig.php +++ b/packages/core/src/Kernel/LoadConfig.php @@ -4,9 +4,10 @@ namespace Tempest\Core\Kernel; +use Tempest\Container\Container; use Tempest\Core\ConfigCache; use Tempest\Core\Environment; -use Tempest\Core\Kernel; +use Tempest\Discovery\DiscoveryConfig; use Tempest\Support\Arr\MutableArray; use Tempest\Support\Filesystem; use Tempest\Support\Path; @@ -18,7 +19,8 @@ final readonly class LoadConfig { public function __construct( - private Kernel $kernel, + private DiscoveryConfig $discoveryConfig, + private Container $container, private ConfigCache $cache, private Environment $environment, ) {} @@ -30,7 +32,12 @@ public function __invoke(): void foreach ($configPaths as $path) { $configFile = require $path; - $this->kernel->container->config($configFile); + if ($configFile instanceof DiscoveryConfig) { + $configFile->locations = [...$this->discoveryConfig->locations, ...$configFile->locations]; + $configFile->classes = [...$this->discoveryConfig->classes, ...$configFile->classes]; + } + + $this->container->config($configFile); } } @@ -42,7 +49,7 @@ public function find(): array $configPaths = new MutableArray(); // Scan for config files in all discovery locations - foreach ($this->kernel->discoveryLocations as $discoveryLocation) { + foreach ($this->discoveryConfig->locations as $discoveryLocation) { $this->scan($discoveryLocation->path, $configPaths); } diff --git a/packages/core/src/PublishesFiles.php b/packages/core/src/PublishesFiles.php index 410d5d156b..c4f10a4c6b 100644 --- a/packages/core/src/PublishesFiles.php +++ b/packages/core/src/PublishesFiles.php @@ -9,6 +9,7 @@ use Tempest\Console\Exceptions\ConsoleException; use Tempest\Console\HasConsole; use Tempest\Container\Inject; +use Tempest\Discovery\Composer; use Tempest\Discovery\SkipDiscovery; use Tempest\Generation\Php\ClassManipulator; use Tempest\Generation\Php\DataObjects\StubFile; diff --git a/packages/core/src/functions.php b/packages/core/src/functions.php index 3e93e0267e..30845336c4 100644 --- a/packages/core/src/functions.php +++ b/packages/core/src/functions.php @@ -6,11 +6,10 @@ use Closure; use Stringable; -use Tempest\Container; -use Tempest\Core\Composer; use Tempest\Core\DeferredTasks; use Tempest\Core\EnvironmentVariableValidationFailed; use Tempest\Core\Kernel; +use Tempest\Discovery\Composer; use Tempest\Intl\Translator; use Tempest\Support\Namespace\PathCouldNotBeMappedToNamespace; use Tempest\Validation\Rule; diff --git a/packages/discovery/composer.json b/packages/discovery/composer.json index 4c668e62a2..b1232690a7 100644 --- a/packages/discovery/composer.json +++ b/packages/discovery/composer.json @@ -6,11 +6,22 @@ "require": { "php": "^8.5", "tempest/reflection": "3.x-dev", - "tempest/support": "3.x-dev" + "tempest/support": "3.x-dev", + "psr/container": "^2.0", + "symfony/cache": "^7.3" + }, + "require-dev": { + "tempest/container": "3.x-dev", + "php-di/php-di": "^7.0" }, "autoload": { "psr-4": { "Tempest\\Discovery\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "Tempest\\Discovery\\Tests\\": "tests" + } } } diff --git a/packages/core/src/Kernel/LoadDiscoveryLocations.php b/packages/discovery/src/AutoloadDiscoveryLocations.php similarity index 81% rename from packages/core/src/Kernel/LoadDiscoveryLocations.php rename to packages/discovery/src/AutoloadDiscoveryLocations.php index 76364d0f74..711b71d483 100644 --- a/packages/core/src/Kernel/LoadDiscoveryLocations.php +++ b/packages/discovery/src/AutoloadDiscoveryLocations.php @@ -2,31 +2,35 @@ declare(strict_types=1); -namespace Tempest\Core\Kernel; +namespace Tempest\Discovery; -use Tempest\Core\Composer; -use Tempest\Core\Kernel; -use Tempest\Discovery\DiscoveryLocation; -use Tempest\Discovery\DiscoveryLocationCouldNotBeLoaded; use Tempest\Support\Filesystem; use function Tempest\Support\Path\normalize; -/** @internal */ -final readonly class LoadDiscoveryLocations +final readonly class AutoloadDiscoveryLocations { + private Composer $composer; + public function __construct( - private Kernel $kernel, - private Composer $composer, - ) {} + private string $rootPath, + ?Composer $composer = null, + ) { + if (! $composer) { + $composer = new Composer($rootPath); + $composer->load(); + } + + $this->composer = $composer; + } - public function __invoke(): void + /** @return \Tempest\Discovery\DiscoveryLocation[] */ + public function __invoke(): array { - $this->kernel->discoveryLocations = [ + return [ ...$this->discoverCorePackages(), ...$this->discoverVendorPackages(), ...$this->discoverAppNamespaces(), - ...$this->kernel->discoveryLocations, ]; } @@ -35,7 +39,7 @@ public function __invoke(): void */ private function discoverCorePackages(): array { - $composerPath = normalize($this->kernel->root, 'vendor/composer'); + $composerPath = normalize($this->rootPath, 'vendor/composer'); $installed = $this->loadJsonFile(normalize($composerPath, 'installed.json')); $packages = $installed['packages'] ?? []; @@ -69,7 +73,7 @@ private function discoverAppNamespaces(): array $discoveredLocations = []; foreach ($this->composer->namespaces as $namespace) { - $path = normalize($this->kernel->root, $namespace->path); + $path = normalize($this->rootPath, $namespace->path); $discoveredLocations[] = new DiscoveryLocation($namespace->namespace, $path); } @@ -82,7 +86,7 @@ private function discoverAppNamespaces(): array */ private function discoverVendorPackages(): array { - $composerPath = normalize($this->kernel->root, 'vendor/composer'); + $composerPath = normalize($this->rootPath, 'vendor/composer'); $installed = $this->loadJsonFile(normalize($composerPath, 'installed.json')); $packages = $installed['packages'] ?? []; diff --git a/packages/core/src/Kernel/LoadDiscoveryClasses.php b/packages/discovery/src/BootDiscovery.php similarity index 87% rename from packages/core/src/Kernel/LoadDiscoveryClasses.php rename to packages/discovery/src/BootDiscovery.php index f6ae0b8cf6..4e2c9d1ee0 100644 --- a/packages/core/src/Kernel/LoadDiscoveryClasses.php +++ b/packages/discovery/src/BootDiscovery.php @@ -2,34 +2,23 @@ declare(strict_types=1); -namespace Tempest\Core\Kernel; +namespace Tempest\Discovery; use AssertionError; -use Tempest\Container\Container; -use Tempest\Core\DiscoveryCache; -use Tempest\Core\DiscoveryCacheStrategy; -use Tempest\Core\DiscoveryConfig; -use Tempest\Core\DiscoveryDiscovery; -use Tempest\Core\Kernel; -use Tempest\Discovery\DiscoversPath; -use Tempest\Discovery\Discovery; -use Tempest\Discovery\DiscoveryItems; -use Tempest\Discovery\DiscoveryLocation; -use Tempest\Discovery\SkipDiscovery; +use Psr\Container\ContainerInterface; use Tempest\Reflection\ClassReflector; use Tempest\Support\Filesystem; use Throwable; -/** @internal */ -final class LoadDiscoveryClasses +final class BootDiscovery { private array $appliedDiscovery = []; private array $shouldSkipForClass = []; public function __construct( - private readonly Container $container, - private readonly DiscoveryConfig $discoveryConfig, - private readonly DiscoveryCache $discoveryCache, + private readonly ContainerInterface $container, + private readonly DiscoveryConfig $config, + private readonly DiscoveryCache $cache = new DiscoveryCache(DiscoveryCacheStrategy::NONE), ) {} /** @@ -56,13 +45,12 @@ public function build( ?array $discoveryClasses = null, ?array $discoveryLocations = null, ): array { - $kernel = $this->container->get(Kernel::class); - - $discoveryLocations ??= $kernel->discoveryLocations; + $discoveryLocations ??= $this->config->locations; if ($discoveryClasses === null) { // DiscoveryDiscovery needs to be applied before we can build all other discoveries - $discoveryDiscovery = $this->resolveDiscovery(DiscoveryDiscovery::class); + $discoveryDiscovery = new DiscoveryDiscovery($this->config); + $discoveryDiscovery->setItems(new DiscoveryItems()); // The first pass over all directories to find all discovery classes $this->discover([$discoveryDiscovery], $discoveryLocations); @@ -73,7 +61,7 @@ public function build( // Resolve all other discoveries from the container, optionally loading their cache $discoveries = array_map( fn (string $discoveryClass) => $this->resolveDiscovery($discoveryClass), - $kernel->discoveryClasses, + $this->config->classes, ); // The second pass over all directories to apply all other discovery classes @@ -115,7 +103,7 @@ private function restoreFromCache(array $discoveries, DiscoveryLocation $locatio return false; } - $cachedForLocation = $this->discoveryCache->restore($location); + $cachedForLocation = $this->cache->restore($location); if (! $this->isCachedLocationUsable($discoveries, $cachedForLocation)) { return false; @@ -190,11 +178,8 @@ private function discoverPath(string $input, DiscoveryLocation $location, array $pathInfo = pathinfo($input); $extension = $pathInfo['extension'] ?? null; $fileName = $pathInfo['filename'] ?: null; - $className = null; // If this is a PHP file starting with an uppercase letter, we assume it's a class. - // TODO: Figure out if we can refactor this to checking composer's autoload map (it might not always be available) - // An other idea is to check whether composer has a check to verify whether a file is a class? if ($extension === 'php' && ucfirst($fileName) === $fileName) { $className = $location->toClassName($input); @@ -265,6 +250,9 @@ private function discoverPath(string $input, DiscoveryLocation $location, array /** * Create a discovery instance from a class name. * Optionally set the cached discovery items whenever caching is enabled. + * @template T of Discovery + * @param class-string $discoveryClass + * @return T */ private function resolveDiscovery(string $discoveryClass): Discovery { @@ -299,7 +287,7 @@ private function shouldSkipBasedOnConfig(ClassReflector|string $input): bool $input = $input->getName(); } - return $this->discoveryConfig->shouldSkip($input); + return $this->config->shouldSkip($input); } /** @@ -307,11 +295,11 @@ private function shouldSkipBasedOnConfig(ClassReflector|string $input): bool */ private function isLocationCached(DiscoveryLocation $location): bool { - if (! $this->discoveryCache->enabled) { + if (! $this->cache->enabled) { return false; } - return match ($this->discoveryCache->strategy) { + return match ($this->cache->strategy) { // If discovery cache is disabled, no locations should be skipped, all should always be discovered DiscoveryCacheStrategy::NONE, DiscoveryCacheStrategy::INVALID => false, // If discover cache is enabled, all locations cache should be skipped diff --git a/packages/core/src/Composer.php b/packages/discovery/src/Composer.php similarity index 96% rename from packages/core/src/Composer.php rename to packages/discovery/src/Composer.php index e3c6917b9a..128ba12bc8 100644 --- a/packages/core/src/Composer.php +++ b/packages/discovery/src/Composer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tempest\Core; +namespace Tempest\Discovery; use Tempest\Process\ProcessExecutor; use Tempest\Support\Arr; @@ -105,7 +105,7 @@ public function addNamespace(string $namespace, string $path): self public function save(): self { - Filesystem\write_json($this->composerPath, $this->composer, pretty: true); + Filesystem\write_json($this->composerPath, $this->composer); return $this; } @@ -124,7 +124,7 @@ public function executeUpdate(): self private function loadComposerFile(string $path): array { if (! Filesystem\is_file($path)) { - throw new ComposerJsonCouldNotBeLocated('Could not locate composer.json.'); + throw new ComposerJsonCouldNotBeLocated("Could not locate {$path}"); } return Filesystem\read_json($path); diff --git a/packages/core/src/ComposerJsonCouldNotBeLocated.php b/packages/discovery/src/ComposerJsonCouldNotBeLocated.php similarity index 79% rename from packages/core/src/ComposerJsonCouldNotBeLocated.php rename to packages/discovery/src/ComposerJsonCouldNotBeLocated.php index 10bf4b2c7d..09f1e8de1e 100644 --- a/packages/core/src/ComposerJsonCouldNotBeLocated.php +++ b/packages/discovery/src/ComposerJsonCouldNotBeLocated.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tempest\Core; +namespace Tempest\Discovery; use Exception; diff --git a/packages/core/src/Config/discovery.config.php b/packages/discovery/src/Config/discovery.config.php similarity index 62% rename from packages/core/src/Config/discovery.config.php rename to packages/discovery/src/Config/discovery.config.php index 447b2d232e..0ab1f56186 100644 --- a/packages/core/src/Config/discovery.config.php +++ b/packages/discovery/src/Config/discovery.config.php @@ -2,6 +2,6 @@ declare(strict_types=1); -use Tempest\Core\DiscoveryConfig; +use Tempest\Discovery\DiscoveryConfig; return new DiscoveryConfig(); diff --git a/packages/core/src/CouldNotStoreDiscoveryCache.php b/packages/discovery/src/CouldNotStoreDiscoveryCache.php similarity index 86% rename from packages/core/src/CouldNotStoreDiscoveryCache.php rename to packages/discovery/src/CouldNotStoreDiscoveryCache.php index 0fdd882457..0c93a63e1d 100644 --- a/packages/core/src/CouldNotStoreDiscoveryCache.php +++ b/packages/discovery/src/CouldNotStoreDiscoveryCache.php @@ -1,9 +1,8 @@ pool = $pool ?? new PhpFilesAdapter( - directory: internal_storage_path('cache/discovery'), + directory: self::getCachePath(), ); } @@ -101,4 +98,13 @@ public static function getCurrentDiscoverStrategyCachePath(): string return __DIR__ . '/current_discovery_strategy'; } } + + private static function getCachePath(): string + { + try { + return internal_storage_path('cache/discovery'); + } catch (Throwable) { + return __DIR__ . '/../.tempest/cache'; + } + } } diff --git a/packages/core/src/DiscoveryCacheInitializer.php b/packages/discovery/src/DiscoveryCacheInitializer.php similarity index 98% rename from packages/core/src/DiscoveryCacheInitializer.php rename to packages/discovery/src/DiscoveryCacheInitializer.php index 0370519a66..4ccde362af 100644 --- a/packages/core/src/DiscoveryCacheInitializer.php +++ b/packages/discovery/src/DiscoveryCacheInitializer.php @@ -1,6 +1,6 @@ > The loaded discovery classes that will be used during discovery */ + public array $classes = []; + + public function __construct( + /** @var \Tempest\Discovery\DiscoveryLocation[] Locations that should be scanned during discovery */ + public array $locations = [], + ) {} + + public static function autoload(string $path): self + { + return new self( + locations: (new AutoloadDiscoveryLocations($path))(), + ); + } + public function shouldSkip(string $input): bool { return $this->skipDiscovery[$input] ?? false; diff --git a/packages/core/src/DiscoveryDiscovery.php b/packages/discovery/src/DiscoveryDiscovery.php similarity index 73% rename from packages/core/src/DiscoveryDiscovery.php rename to packages/discovery/src/DiscoveryDiscovery.php index 07a3f66d5f..0a60a238e5 100644 --- a/packages/core/src/DiscoveryDiscovery.php +++ b/packages/discovery/src/DiscoveryDiscovery.php @@ -2,11 +2,8 @@ declare(strict_types=1); -namespace Tempest\Core; +namespace Tempest\Discovery; -use Tempest\Discovery\Discovery; -use Tempest\Discovery\DiscoveryLocation; -use Tempest\Discovery\IsDiscovery; use Tempest\Reflection\ClassReflector; final class DiscoveryDiscovery implements Discovery @@ -14,7 +11,7 @@ final class DiscoveryDiscovery implements Discovery use IsDiscovery; public function __construct( - private readonly Kernel $kernel, + private readonly DiscoveryConfig $config, ) {} public function discover(DiscoveryLocation $location, ClassReflector $class): void @@ -33,7 +30,7 @@ public function discover(DiscoveryLocation $location, ClassReflector $class): vo public function apply(): void { foreach ($this->discoveryItems as $className) { - $this->kernel->discoveryClasses[] = $className; + $this->config->classes[] = $className; } } } diff --git a/packages/discovery/tests/DiscoveryTest.php b/packages/discovery/tests/DiscoveryTest.php new file mode 100644 index 0000000000..1a424a6fbb --- /dev/null +++ b/packages/discovery/tests/DiscoveryTest.php @@ -0,0 +1,57 @@ +assertNotNull(MyDiscoveryClass::$discoveredItem); + $this->assertSame('check', MyDiscoveryClass::$discoveredItem->name); + } + + public function test_discovery_with_other_container(): void + { + $container = new Container(); + + (new BootDiscovery( + container: $container, + config: new DiscoveryConfig(locations: [ + new DiscoveryLocation( + namespace: 'Tempest\Discovery\Tests\Fixtures', + path: __DIR__ . '/Fixtures', + ), + ]), + ))(); + + $this->assertNotNull(MyDiscoveryClass::$discoveredItem); + $this->assertSame('check', MyDiscoveryClass::$discoveredItem->name); + } +} diff --git a/packages/discovery/tests/Fixtures/DiscoveredItem.php b/packages/discovery/tests/Fixtures/DiscoveredItem.php new file mode 100644 index 0000000000..779b8676b0 --- /dev/null +++ b/packages/discovery/tests/Fixtures/DiscoveredItem.php @@ -0,0 +1,10 @@ +is(DiscoveredItem::class)) { + $this->discoveryItems->add($location, $class); + } + } + + public function apply(): void + { + /** @var ClassReflector $class */ + foreach ($this->discoveryItems as $class) { + self::$discoveredItem = $class->newInstanceArgs(['name' => 'check']); + } + } +} diff --git a/packages/generation/composer.json b/packages/generation/composer.json index d25bd45cac..ac4da40e6e 100644 --- a/packages/generation/composer.json +++ b/packages/generation/composer.json @@ -8,7 +8,9 @@ "nette/php-generator": "4.2.1", "nette/schema": "^1.3.4", "nikic/php-parser": "^5.3", - "tempest/support": "3.x-dev" + "tempest/container": "3.x-dev", + "tempest/support": "3.x-dev", + "psr/container": "^2.0" }, "autoload": { "psr-4": { diff --git a/packages/support/composer.json b/packages/support/composer.json index 0f4ea97577..c2b69a31b4 100644 --- a/packages/support/composer.json +++ b/packages/support/composer.json @@ -5,7 +5,6 @@ "minimum-stability": "dev", "require": { "php": "^8.5", - "tempest/container": "3.x-dev", "voku/portable-ascii": "^2.0.3", "symfony/uid": "^7.1" }, diff --git a/packages/upgrade/config/sets/level/up-to-tempest-34.php b/packages/upgrade/config/sets/level/up-to-tempest-34.php new file mode 100644 index 0000000000..4d52182503 --- /dev/null +++ b/packages/upgrade/config/sets/level/up-to-tempest-34.php @@ -0,0 +1,15 @@ +sets([ + TempestSetList::TEMPEST_20, + TempestSetList::TEMPEST_28, + TempestSetList::TEMPEST_30, + TempestSetList::TEMPEST_34, + ]); +}; diff --git a/packages/upgrade/config/sets/tempest34.php b/packages/upgrade/config/sets/tempest34.php new file mode 100644 index 0000000000..41d2aa0b0d --- /dev/null +++ b/packages/upgrade/config/sets/tempest34.php @@ -0,0 +1,15 @@ +rule(UpdateDiscoveryImportsRector::class); + $config->rule(UpdateKernelDiscoveryPropertiesRector::class); +}; diff --git a/packages/upgrade/src/Set/TempestLevelSetList.php b/packages/upgrade/src/Set/TempestLevelSetList.php index 859b32034c..93c6365044 100644 --- a/packages/upgrade/src/Set/TempestLevelSetList.php +++ b/packages/upgrade/src/Set/TempestLevelSetList.php @@ -9,4 +9,5 @@ final class TempestLevelSetList public const string UP_TO_TEMPEST_20 = __DIR__ . '/../../config/sets/level/up-to-tempest-20.php'; public const string UP_TO_TEMPEST_28 = __DIR__ . '/../../config/sets/level/up-to-tempest-28.php'; public const string UP_TO_TEMPEST_30 = __DIR__ . '/../../config/sets/level/up-to-tempest-30.php'; + public const string UP_TO_TEMPEST_34 = __DIR__ . '/../../config/sets/level/up-to-tempest-34.php'; } diff --git a/packages/upgrade/src/Set/TempestSetList.php b/packages/upgrade/src/Set/TempestSetList.php index 8c4bcaa293..c23d6f1736 100644 --- a/packages/upgrade/src/Set/TempestSetList.php +++ b/packages/upgrade/src/Set/TempestSetList.php @@ -9,4 +9,5 @@ final class TempestSetList public const string TEMPEST_20 = __DIR__ . '/../../config/sets/tempest20.php'; public const string TEMPEST_28 = __DIR__ . '/../../config/sets/tempest28.php'; public const string TEMPEST_30 = __DIR__ . '/../../config/sets/tempest30.php'; + public const string TEMPEST_34 = __DIR__ . '/../../config/sets/tempest34.php'; } diff --git a/packages/upgrade/src/Tempest34/UpdateDiscoveryImportsRector.php b/packages/upgrade/src/Tempest34/UpdateDiscoveryImportsRector.php new file mode 100644 index 0000000000..979ac129d7 --- /dev/null +++ b/packages/upgrade/src/Tempest34/UpdateDiscoveryImportsRector.php @@ -0,0 +1,52 @@ + 'Tempest\Discovery\DiscoveryCache', + 'Tempest\Core\DiscoveryCacheStrategy' => 'Tempest\Discovery\DiscoveryCacheStrategy', + 'Tempest\Core\Composer' => 'Tempest\Discovery\Composer', + 'Tempest\Core\ComposerJsonCouldNotBeLocated' => 'Tempest\Discovery\ComposerJsonCouldNotBeLocated', + 'Tempest\Core\DiscoveryCachingStrategyWasChanged' => 'Tempest\Discovery\DiscoveryCachingStrategyWasChanged', + 'Tempest\Core\DiscoveryConfig' => 'Tempest\Discovery\DiscoveryConfig', + 'Tempest\Core\CouldNotStoreDiscoveryCache' => 'Tempest\Discovery\CouldNotStoreDiscoveryCache', + ]; + + public function getNodeTypes(): array + { + return [ + Node\UseItem::class, + Node\Name\FullyQualified::class, + ]; + } + + public function refactor(Node $node): ?Node + { + if ($node instanceof Node\UseItem) { + $name = $node->name->toString(); + + if (isset(self::CLASS_RENAMES[$name])) { + $node->name = new Node\Name(self::CLASS_RENAMES[$name]); + + return $node; + } + + return null; + } + + if ($node instanceof Node\Name\FullyQualified) { + $name = $node->toString(); + + if (isset(self::CLASS_RENAMES[$name])) { + return new Node\Name\FullyQualified(self::CLASS_RENAMES[$name]); + } + } + + return null; + } +} diff --git a/packages/upgrade/src/Tempest34/UpdateKernelDiscoveryPropertiesRector.php b/packages/upgrade/src/Tempest34/UpdateKernelDiscoveryPropertiesRector.php new file mode 100644 index 0000000000..bbae2e5811 --- /dev/null +++ b/packages/upgrade/src/Tempest34/UpdateKernelDiscoveryPropertiesRector.php @@ -0,0 +1,63 @@ + 'locations', + 'discoveryClasses' => 'classes', + ]; + + public function getNodeTypes(): array + { + return [ + PropertyFetch::class, + ]; + } + + public function refactor(Node $node): ?Node + { + if (! $node instanceof PropertyFetch) { + return null; + } + + if (! $node->name instanceof Identifier) { + return null; + } + + $propertyName = $node->name->toString(); + + if (! isset(self::PROPERTY_RENAMES[$propertyName])) { + return null; + } + + if (! $this->isKernelType($node->var)) { + return null; + } + + // Transform $kernel->discoveryLocations to $kernel->registry->locations + return new PropertyFetch( + new PropertyFetch($node->var, 'registry'), + self::PROPERTY_RENAMES[$propertyName], + ); + } + + private function isKernelType(Node\Expr $expr): bool + { + $type = $this->nodeTypeResolver->getType($expr); + + foreach ($type->getObjectClassNames() as $className) { + if ($className === 'Tempest\Core\Kernel' || is_subclass_of($className, 'Tempest\Core\Kernel')) { + return true; + } + } + + return false; + } +} diff --git a/packages/upgrade/tests/Tempest34/Fixtures/ComposerJsonCouldNotBeLocatedNamespaceChange.input.php b/packages/upgrade/tests/Tempest34/Fixtures/ComposerJsonCouldNotBeLocatedNamespaceChange.input.php new file mode 100644 index 0000000000..fb2ffeee8d --- /dev/null +++ b/packages/upgrade/tests/Tempest34/Fixtures/ComposerJsonCouldNotBeLocatedNamespaceChange.input.php @@ -0,0 +1,11 @@ +kernel->discoveryClasses; + } +} diff --git a/packages/upgrade/tests/Tempest34/Fixtures/KernelDiscoveryLocations.input.php b/packages/upgrade/tests/Tempest34/Fixtures/KernelDiscoveryLocations.input.php new file mode 100644 index 0000000000..cbf480ccec --- /dev/null +++ b/packages/upgrade/tests/Tempest34/Fixtures/KernelDiscoveryLocations.input.php @@ -0,0 +1,15 @@ +kernel->discoveryLocations; + } +} diff --git a/packages/upgrade/tests/Tempest34/Tempest34RectorTest.php b/packages/upgrade/tests/Tempest34/Tempest34RectorTest.php new file mode 100644 index 0000000000..1a8c0ad324 --- /dev/null +++ b/packages/upgrade/tests/Tempest34/Tempest34RectorTest.php @@ -0,0 +1,93 @@ + new RectorTester(__DIR__ . '/tempest34_rector.php'); + } + + public function test_discovery_cache_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/DiscoveryCacheNamespaceChange.input.php') + ->assertContains('use Tempest\Discovery\DiscoveryCache;') + ->assertNotContains('use Tempest\Core\DiscoveryCache;'); + } + + public function test_discovery_cache_strategy_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/DiscoveryCacheStrategyNamespaceChange.input.php') + ->assertContains('use Tempest\Discovery\DiscoveryCacheStrategy;') + ->assertNotContains('use Tempest\Core\DiscoveryCacheStrategy;'); + } + + public function test_composer_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/ComposerNamespaceChange.input.php') + ->assertContains('use Tempest\Discovery\Composer;') + ->assertNotContains('use Tempest\Core\Composer;'); + } + + public function test_composer_json_could_not_be_located_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/ComposerJsonCouldNotBeLocatedNamespaceChange.input.php') + ->assertContains('use Tempest\Discovery\ComposerJsonCouldNotBeLocated;') + ->assertNotContains('use Tempest\Core\ComposerJsonCouldNotBeLocated;'); + } + + public function test_could_not_store_discovery_cache_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/CouldNotStoreDiscoveryCacheNamespaceChange.input.php') + ->assertContains('use Tempest\Discovery\CouldNotStoreDiscoveryCache;') + ->assertNotContains('use Tempest\Core\CouldNotStoreDiscoveryCache;'); + } + + public function test_discovery_caching_strategy_was_changed_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/DiscoveryCachingStrategyWasChangedNamespaceChange.input.php') + ->assertContains('use Tempest\Discovery\DiscoveryCachingStrategyWasChanged;') + ->assertNotContains('use Tempest\Core\DiscoveryCachingStrategyWasChanged;'); + } + + public function test_discovery_config_namespace_change(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/DiscoveryConfigNamespaceChange.input.php') + ->assertContains('use Tempest\Discovery\DiscoveryConfig;') + ->assertNotContains('use Tempest\Core\DiscoveryConfig;'); + } + + public function test_fully_qualified_discovery_cache(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/FullyQualifiedDiscoveryCache.input.php') + ->assertContains('Tempest\Discovery\DiscoveryCache') + ->assertNotContains('Tempest\Core\DiscoveryCache'); + } + + public function test_kernel_discovery_locations_refactored(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/KernelDiscoveryLocations.input.php') + ->assertContains('$this->kernel->registry->locations') + ->assertNotContains('$this->kernel->discoveryLocations'); + } + + public function test_kernel_discovery_classes_refactored(): void + { + $this->rector + ->runFixture(__DIR__ . '/Fixtures/KernelDiscoveryClasses.input.php') + ->assertContains('$this->kernel->registry->classes') + ->assertNotContains('$this->kernel->discoveryClasses'); + } +} diff --git a/packages/upgrade/tests/Tempest34/tempest34_rector.php b/packages/upgrade/tests/Tempest34/tempest34_rector.php new file mode 100644 index 0000000000..bdd47e5fe3 --- /dev/null +++ b/packages/upgrade/tests/Tempest34/tempest34_rector.php @@ -0,0 +1,9 @@ +withSets([TempestSetList::TEMPEST_34]); diff --git a/packages/view/src/Initializers/TempestViewCompilerInitializer.php b/packages/view/src/Initializers/TempestViewCompilerInitializer.php index 34036052ec..c43e6c65ff 100644 --- a/packages/view/src/Initializers/TempestViewCompilerInitializer.php +++ b/packages/view/src/Initializers/TempestViewCompilerInitializer.php @@ -5,7 +5,7 @@ use Tempest\Container\Container; use Tempest\Container\Initializer; use Tempest\Container\Singleton; -use Tempest\Core\Kernel; +use Tempest\Discovery\DiscoveryConfig; use Tempest\View\Attributes\AttributeFactory; use Tempest\View\Elements\ElementFactory; use Tempest\View\Parser\TempestViewCompiler; @@ -18,7 +18,7 @@ public function initialize(Container $container): TempestViewCompiler return new TempestViewCompiler( elementFactory: $container->get(ElementFactory::class), attributeFactory: $container->get(AttributeFactory::class), - discoveryLocations: $container->get(Kernel::class)->discoveryLocations, + discoveryLocations: $container->get(DiscoveryConfig::class)->locations, ); } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2d624b923b..7d2fa08689 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -72,7 +72,7 @@ parameters: # Intentional: dd() delegates to ld() and dump() delegates to lw() - identifier: disallowed.function - path: packages/core/src/Composer.php + path: packages/discovery/src/Composer.php count: 1 # Intentional: exec() is required for running composer commands - diff --git a/rector.php b/rector.php index b511c8c6c9..5f13cad104 100644 --- a/rector.php +++ b/rector.php @@ -29,6 +29,7 @@ __DIR__ . '/src', __DIR__ . '/tests', ]) + ->withSkipPath(__DIR__ . '/tests/PHPStan/QueryFunctionDynamicReturnTypeExtension.php') ->withConfiguredRule(AddSensitiveParameterAttributeRector::class, [ 'sensitive_parameters' => [ 'password', diff --git a/src/Tempest/Framework/Testing/InstallerTester.php b/src/Tempest/Framework/Testing/InstallerTester.php index de4c57027d..1032853ab6 100644 --- a/src/Tempest/Framework/Testing/InstallerTester.php +++ b/src/Tempest/Framework/Testing/InstallerTester.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Assert; use Tempest\Container\Container; -use Tempest\Core\Composer; use Tempest\Core\FrameworkKernel; +use Tempest\Discovery\Composer; use Tempest\Process\Testing\ProcessTester; use Tempest\Support\Arr; use Tempest\Support\Filesystem; diff --git a/src/Tempest/Framework/Testing/IntegrationTest.php b/src/Tempest/Framework/Testing/IntegrationTest.php index 7c600b78ba..4bccdfa35f 100644 --- a/src/Tempest/Framework/Testing/IntegrationTest.php +++ b/src/Tempest/Framework/Testing/IntegrationTest.php @@ -18,7 +18,6 @@ use Tempest\Container\GenericContainer; use Tempest\Core\Exceptions\ExceptionTester; use Tempest\Core\FrameworkKernel; -use Tempest\Core\Kernel; use Tempest\Database\Testing\DatabaseTester; use Tempest\DateTime\DateTimeInterface; use Tempest\Discovery\DiscoveryLocation; @@ -50,7 +49,7 @@ abstract class IntegrationTest extends TestCase /** @var \Tempest\Discovery\DiscoveryLocation[] */ protected array $discoveryLocations = []; - protected Kernel $kernel; + protected FrameworkKernel $kernel; protected GenericContainer $container; diff --git a/tests/Benchmark/Discovery/DiscoveryScanBench.php b/tests/Benchmark/Discovery/DiscoveryScanBench.php index 8376dbdf67..cc6b2102ee 100644 --- a/tests/Benchmark/Discovery/DiscoveryScanBench.php +++ b/tests/Benchmark/Discovery/DiscoveryScanBench.php @@ -10,12 +10,12 @@ use PhpBench\Attributes\Revs; use PhpBench\Attributes\Warmup; use Tempest\Container\Container; -use Tempest\Core\DiscoveryCache; -use Tempest\Core\DiscoveryCacheStrategy; -use Tempest\Core\DiscoveryConfig; use Tempest\Core\FrameworkKernel; -use Tempest\Core\Kernel\LoadDiscoveryClasses; +use Tempest\Discovery\BootDiscovery; use Tempest\Discovery\Discovery; +use Tempest\Discovery\DiscoveryCache; +use Tempest\Discovery\DiscoveryCacheStrategy; +use Tempest\Discovery\DiscoveryConfig; use Tempest\Discovery\DiscoveryLocation; final class DiscoveryScanBench @@ -30,21 +30,24 @@ final class DiscoveryScanBench private string $root; + private DiscoveryConfig $discoveryConfig; + public function __construct() { $this->root = dirname(__DIR__, 3); $kernel = FrameworkKernel::boot(root: $this->root); $this->container = $kernel->container; - $this->discoveryLocations = $kernel->discoveryLocations; - $this->discoveryClasses = $kernel->discoveryClasses; + $this->discoveryConfig = $kernel->discoveryConfig; + $this->discoveryLocations = $kernel->discoveryConfig->locations; + $this->discoveryClasses = $kernel->discoveryConfig->classes; } - private function createLoader(): LoadDiscoveryClasses + private function createLoader(): BootDiscovery { - return new LoadDiscoveryClasses( + return new BootDiscovery( container: $this->container, - discoveryConfig: new DiscoveryConfig(), - discoveryCache: new DiscoveryCache(DiscoveryCacheStrategy::NONE), + config: $this->discoveryConfig, + cache: new DiscoveryCache(DiscoveryCacheStrategy::NONE), ); } diff --git a/tests/Fixtures/Config/discovery.config.php b/tests/Fixtures/Config/discovery.config.php index 2b07822a49..300d01a443 100644 --- a/tests/Fixtures/Config/discovery.config.php +++ b/tests/Fixtures/Config/discovery.config.php @@ -1,6 +1,6 @@ container->get(ConfigCache::class)->clear(); - $this->kernel->discoveryLocations = [ + $this->container->singleton(DiscoveryConfig::class, new DiscoveryConfig(locations: [ new DiscoveryLocation('App', __DIR__ . '/Fixtures'), - ]; + ])); } public function test_config_loaded_in_order(): void diff --git a/tests/Integration/Core/DiscoveryCacheTest.php b/tests/Integration/Core/DiscoveryCacheTest.php index 270a657ddf..1b51bf1ddd 100644 --- a/tests/Integration/Core/DiscoveryCacheTest.php +++ b/tests/Integration/Core/DiscoveryCacheTest.php @@ -4,9 +4,9 @@ use PHPUnit\Framework\Attributes\PostCondition; use PHPUnit\Framework\Attributes\Test; -use Tempest\Core\CouldNotStoreDiscoveryCache; -use Tempest\Core\DiscoveryCache; -use Tempest\Core\DiscoveryCacheStrategy; +use Tempest\Discovery\CouldNotStoreDiscoveryCache; +use Tempest\Discovery\DiscoveryCache; +use Tempest\Discovery\DiscoveryCacheStrategy; use Tempest\Discovery\DiscoveryLocation; use Tests\Tempest\Integration\Core\Fixtures\TestDiscovery; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; diff --git a/tests/Integration/Core/FunctionsTest.php b/tests/Integration/Core/FunctionsTest.php index c7a1b17e94..132a98489b 100644 --- a/tests/Integration/Core/FunctionsTest.php +++ b/tests/Integration/Core/FunctionsTest.php @@ -5,8 +5,8 @@ namespace Tests\Tempest\Integration\Core; use PHPUnit\Framework\Attributes\TestWith; -use Tempest\Core\Composer; use Tempest\Core\FrameworkKernel; +use Tempest\Discovery\Composer; use Tempest\Support\Namespace\PathCouldNotBeMappedToNamespace; use Tempest\Support\Namespace\Psr4Namespace; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; diff --git a/tests/Integration/Core/KernelTest.php b/tests/Integration/Core/KernelTest.php index 2b810aea54..14ab1da4ef 100644 --- a/tests/Integration/Core/KernelTest.php +++ b/tests/Integration/Core/KernelTest.php @@ -5,8 +5,8 @@ namespace Tests\Tempest\Integration\Core; use PHPUnit\Framework\TestCase; -use Tempest\Container\Container; use Tempest\Core\FrameworkKernel; +use Tempest\Discovery\DiscoveryConfig; use Tempest\Discovery\DiscoveryLocation; use Tests\Tempest\Fixtures\TestDependency; @@ -25,9 +25,8 @@ public function test_discovery_boot(): void ], ); - $this->assertInstanceOf(Container::class, $kernel->container); - - $this->assertNotEmpty($kernel->discoveryClasses); + $discoveryConfig = $kernel->container->get(DiscoveryConfig::class); + $this->assertNotEmpty($discoveryConfig->classes); $test = $kernel->container->get(TestDependency::class); diff --git a/tests/Integration/Core/LoadDiscoveryClassesTest.php b/tests/Integration/Core/LoadDiscoveryClassesTest.php index 29a21db43b..91081a9ad7 100644 --- a/tests/Integration/Core/LoadDiscoveryClassesTest.php +++ b/tests/Integration/Core/LoadDiscoveryClassesTest.php @@ -5,9 +5,9 @@ namespace Tests\Tempest\Integration\Core; use PHPUnit\Framework\Attributes\Test; -use Tempest\Core\Kernel\LoadDiscoveryClasses; use Tempest\Database\MigratesUp; use Tempest\Database\Migrations\RunnableMigrations; +use Tempest\Discovery\BootDiscovery; use Tempest\Discovery\DiscoveryLocation; use Tempest\Support\Arr; use Tests\Tempest\Fixtures\Discovery\HiddenDatabaseMigration; @@ -63,14 +63,14 @@ public function only_load_specific_discovery_classes(): void $dependency = $this->container->get(ManualTestDiscoveryDependency::class); $dependency->discovered = false; - /** @var LoadDiscoveryClasses $loadDiscoveryClasses */ - $loadDiscoveryClasses = $this->container->get(LoadDiscoveryClasses::class); + /** @var \Tempest\Discovery\BootDiscovery $bootDiscovery */ + $bootDiscovery = $this->container->get(BootDiscovery::class); - $loadDiscoveryClasses(discoveryClasses: [], discoveryLocations: []); + $bootDiscovery(discoveryClasses: [], discoveryLocations: []); $this->assertFalse($dependency->discovered); - $loadDiscoveryClasses( + $bootDiscovery( discoveryClasses: [ ManualTestDiscovery::class, ], diff --git a/tests/Integration/Core/PublishesFilesTest.php b/tests/Integration/Core/PublishesFilesTest.php index d1611ed346..1ee6b178ab 100644 --- a/tests/Integration/Core/PublishesFilesTest.php +++ b/tests/Integration/Core/PublishesFilesTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; -use Tempest\Core\Composer; +use Tempest\Discovery\Composer; use Tempest\Support\Namespace\Psr4Namespace; use Tests\Tempest\Fixtures\Core\PublishesFilesConcreteClass; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; diff --git a/tests/Integration/Database/Builder/IsDatabaseModelTest.php b/tests/Integration/Database/Builder/IsDatabaseModelTest.php index 1a312bad0f..f3a60efe7f 100644 --- a/tests/Integration/Database/Builder/IsDatabaseModelTest.php +++ b/tests/Integration/Database/Builder/IsDatabaseModelTest.php @@ -771,6 +771,7 @@ public function test_on_database_does_not_mutate_original(): void // Original still works against default database $foo->update(bar: 'updated'); + $refreshed = Foo::get($foo->id); $this->assertSame('updated', $refreshed->bar); diff --git a/tests/Integration/Framework/Commands/DiscoveryStatusCommandTest.php b/tests/Integration/Framework/Commands/DiscoveryStatusCommandTest.php index b87f9c1875..1db858feef 100644 --- a/tests/Integration/Framework/Commands/DiscoveryStatusCommandTest.php +++ b/tests/Integration/Framework/Commands/DiscoveryStatusCommandTest.php @@ -17,11 +17,11 @@ public function test_discovery_status_command(): void { $output = $this->console->call('discovery:status -cl'); - foreach ($this->kernel->discoveryClasses as $discoveryClass) { + foreach ($this->kernel->discoveryConfig->classes as $discoveryClass) { $output->assertContains(basename(str_replace('\\', '/', $discoveryClass))); } - foreach ($this->kernel->discoveryLocations as $discoveryLocation) { + foreach ($this->kernel->discoveryConfig->locations as $discoveryLocation) { // @TODO(aidan-casey): remove the src/ directory. $output->assertContains(str(realpath($discoveryLocation->path))->afterLast(['src/', 'packages/', 'vendor/', 'tests/'])->toString()); } diff --git a/tests/Integration/Http/Exceptions/ExceptionRendererTest.php b/tests/Integration/Http/Exceptions/ExceptionRendererTest.php index 653695a589..27b73b1f90 100644 --- a/tests/Integration/Http/Exceptions/ExceptionRendererTest.php +++ b/tests/Integration/Http/Exceptions/ExceptionRendererTest.php @@ -52,8 +52,8 @@ public function __construct(FrameworkKernel $kernel) { $this->root = $kernel->root; $this->internalStorage = $kernel->internalStorage; - $this->discoveryLocations = $kernel->discoveryLocations; - $this->discoveryClasses = $kernel->discoveryClasses; + $this->discoveryLocations = $kernel->discoveryConfig->locations; + $this->discoveryClasses = $kernel->discoveryConfig->classes; $this->container = $kernel->container; } diff --git a/tests/Integration/Http/Exceptions/HttpExceptionHandlerTest.php b/tests/Integration/Http/Exceptions/HttpExceptionHandlerTest.php index 370c7d9e1f..7ad74cca00 100644 --- a/tests/Integration/Http/Exceptions/HttpExceptionHandlerTest.php +++ b/tests/Integration/Http/Exceptions/HttpExceptionHandlerTest.php @@ -48,8 +48,8 @@ public function __construct(FrameworkKernel $kernel) { $this->root = $kernel->root; $this->internalStorage = $kernel->internalStorage; - $this->discoveryLocations = $kernel->discoveryLocations; - $this->discoveryClasses = $kernel->discoveryClasses; + $this->discoveryLocations = $kernel->discoveryConfig->locations; + $this->discoveryClasses = $kernel->discoveryConfig->classes; $this->container = $kernel->container; } diff --git a/tests/Integration/View/TempestViewRendererTest.php b/tests/Integration/View/TempestViewRendererTest.php index cbd9aaf583..c0d5b6e5fb 100644 --- a/tests/Integration/View/TempestViewRendererTest.php +++ b/tests/Integration/View/TempestViewRendererTest.php @@ -4,7 +4,7 @@ namespace Tests\Tempest\Integration\View; -use Tempest\Core\Kernel; +use Tempest\Discovery\DiscoveryConfig; use Tempest\Discovery\DiscoveryLocation; use Tempest\Support\Html\HtmlString; use Tempest\View\Exceptions\ElementWasInvalid; @@ -947,10 +947,9 @@ public function test_zero_in_attribute(): void public function test_discovery_locations_are_passed_to_compiler(): void { - /** @var \Tempest\Core\Kernel $kernel */ - $kernel = $this->get(Kernel::class); + $discoveryConfig = $this->get(DiscoveryConfig::class); - $kernel->discoveryLocations[] = new DiscoveryLocation( + $discoveryConfig->locations[] = new DiscoveryLocation( 'Tests\Tempest\Integration\View\Fixtures', __DIR__ . '/Fixtures', ); diff --git a/tests/Integration/Vite/DevelopmentTagsResolverTest.php b/tests/Integration/Vite/DevelopmentTagsResolverTest.php index 20fa380083..510fb08a77 100644 --- a/tests/Integration/Vite/DevelopmentTagsResolverTest.php +++ b/tests/Integration/Vite/DevelopmentTagsResolverTest.php @@ -4,7 +4,7 @@ namespace Tests\Tempest\Integration\Vite; -use Tempest\Core\Composer; +use Tempest\Discovery\Composer; use Tempest\Support\Namespace\Psr4Namespace; use Tempest\Vite\Exceptions\FileSystemEntrypointWasNotFoundException; use Tempest\Vite\TagCompiler\TagCompiler;