diff --git a/packages/framework/src/Foundation/Concerns/HydeExtension.php b/packages/framework/src/Foundation/Concerns/HydeExtension.php new file mode 100644 index 00000000000..9b3bf1669f3 --- /dev/null +++ b/packages/framework/src/Foundation/Concerns/HydeExtension.php @@ -0,0 +1,79 @@ +app->make(HydeKernel::class)->registerExtension(MyExtension::class); ``` + * + * @see \Hyde\Framework\Testing\Feature\HydeExtensionTest + */ +abstract class HydeExtension +{ + /** + * If your extension adds new page classes, you should register them here. + * + * Hyde will then automatically discover source files for the new page class, + * generate routes, and compile the pages during the build process. + * + * @return array> + */ + public static function getPageClasses(): array + { + return []; + } + + /** + * If your extension needs to hook into the file discovery process, + * you can configure the following handler method. It will be called + * at the end of the file discovery process. The collection instance + * will be injected, so that you can interact with it directly. + */ + public static function discoverFiles(FileCollection $collection): void + { + // + } + + /** + * If your extension needs to hook into the page discovery process, + * you can configure the following handler method. It will be called + * at the end of the page discovery process. The collection instance + * will be injected, so that you can interact with it directly. + */ + public static function discoverPages(PageCollection $collection): void + { + // + } + + /** + * If your extension needs to hook into the route discovery process, + * you can configure the following handler method. It will be called + * at the end of the route discovery process. The collection instance + * will be injected, so that you can interact with it directly. + */ + public static function discoverRoutes(RouteCollection $collection): void + { + // + } +} diff --git a/packages/framework/src/Foundation/Concerns/ManagesHydeKernel.php b/packages/framework/src/Foundation/Concerns/ManagesHydeKernel.php index a50cf97ce68..4b016be4527 100644 --- a/packages/framework/src/Foundation/Concerns/ManagesHydeKernel.php +++ b/packages/framework/src/Foundation/Concerns/ManagesHydeKernel.php @@ -54,6 +54,8 @@ public function getSourceRoot(): string * * @experimental This feature is experimental and may change substantially before the 1.0.0 release. * + * @deprecated This feature may be replaced by the {@see \Hyde\Foundation\Concerns\HydeExtension} system. + * * If you are a package developer, and want a custom page class to be discovered, * you'll need to register it sometime before the boot process, before discovery is run. * Typically, you would do this by calling this method in the register method of a service provider. @@ -89,4 +91,18 @@ public function getRegisteredPageClasses(): array { return $this->pageClasses; } + + /** @param class-string<\Hyde\Foundation\Concerns\HydeExtension> $extension */ + public function registerExtension(string $extension): void + { + if (! in_array($extension, $this->extensions, true)) { + $this->extensions[] = $extension; + } + } + + /** @return array> */ + public function getRegisteredExtensions(): array + { + return $this->extensions; + } } diff --git a/packages/framework/src/Foundation/FileCollection.php b/packages/framework/src/Foundation/FileCollection.php index 352210c0f81..cd0bd9598c3 100644 --- a/packages/framework/src/Foundation/FileCollection.php +++ b/packages/framework/src/Foundation/FileCollection.php @@ -85,6 +85,11 @@ protected function runDiscovery(): self $this->discoverFilesFor($pageClass); } + /** @var class-string<\Hyde\Foundation\Concerns\HydeExtension> $extension */ + foreach ($this->kernel->getRegisteredExtensions() as $extension) { + $extension::discoverFiles($this); + } + $this->discoverMediaAssetFiles(); return $this; diff --git a/packages/framework/src/Foundation/HydeKernel.php b/packages/framework/src/Foundation/HydeKernel.php index 30c2a5b4aa2..79be4ea9fd6 100644 --- a/packages/framework/src/Foundation/HydeKernel.php +++ b/packages/framework/src/Foundation/HydeKernel.php @@ -58,6 +58,8 @@ class HydeKernel implements SerializableContract protected bool $booted = false; protected array $pageClasses = []; + protected array $extensions = []; + final public const VERSION = '1.0.0-dev'; public function __construct(?string $basePath = null, string $sourceRoot = '') diff --git a/packages/framework/src/Foundation/PageCollection.php b/packages/framework/src/Foundation/PageCollection.php index 219fe90e4c9..294bd2e87b1 100644 --- a/packages/framework/src/Foundation/PageCollection.php +++ b/packages/framework/src/Foundation/PageCollection.php @@ -88,6 +88,11 @@ protected function runDiscovery(): self $this->discoverPagesFor($pageClass); } + /** @var class-string<\Hyde\Foundation\Concerns\HydeExtension> $extension */ + foreach ($this->kernel->getRegisteredExtensions() as $extension) { + $extension::discoverPages($this); + } + return $this; } diff --git a/packages/framework/src/Foundation/RouteCollection.php b/packages/framework/src/Foundation/RouteCollection.php index d7d5cc5878d..d6db83225ef 100644 --- a/packages/framework/src/Foundation/RouteCollection.php +++ b/packages/framework/src/Foundation/RouteCollection.php @@ -78,6 +78,11 @@ protected function runDiscovery(): self $this->discover($page); }); + /** @var class-string<\Hyde\Foundation\Concerns\HydeExtension> $extension */ + foreach ($this->kernel->getRegisteredExtensions() as $extension) { + $extension::discoverRoutes($this); + } + return $this; } } diff --git a/packages/framework/tests/Feature/HydeExtensionFeatureTest.php b/packages/framework/tests/Feature/HydeExtensionFeatureTest.php new file mode 100644 index 00000000000..a3256e9d4ab --- /dev/null +++ b/packages/framework/tests/Feature/HydeExtensionFeatureTest.php @@ -0,0 +1,158 @@ +kernel = HydeKernel::getInstance(); + } + + public function testBaseClassGetPageClasses() + { + $this->assertSame([], HydeExtension::getPageClasses()); + } + + public function testBaseClassDiscoveryHandlers() + { + HydeExtension::discoverFiles(Hyde::files()); + HydeExtension::discoverPages(Hyde::pages()); + HydeExtension::discoverRoutes(Hyde::routes()); + + $this->markTestSuccessful(); + } + + public function testCanRegisterNewExtension() + { + $this->kernel->registerExtension(HydeTestExtension::class); + $this->assertSame([HydeTestExtension::class], $this->kernel->getRegisteredExtensions()); + } + + public function testHandlerMethodsAreCalledByDiscovery() + { + $this->kernel->registerExtension(HydeTestExtension::class); + + $this->assertSame([], HydeTestExtension::$callCache); + + $this->kernel->boot(); + + $this->assertSame(['files', 'pages', 'routes'], HydeTestExtension::$callCache); + + HydeTestExtension::$callCache = []; + } + + public function testFileHandlerDependencyInjection() + { + $this->kernel->registerExtension(SpyableTestExtension::class); + $this->kernel->boot(); + + $this->assertInstanceOf(FileCollection::class, ...SpyableTestExtension::getCalled('files')); + } + + public function testPageHandlerDependencyInjection() + { + $this->kernel->registerExtension(SpyableTestExtension::class); + $this->kernel->boot(); + + $this->assertInstanceOf(PageCollection::class, ...SpyableTestExtension::getCalled('pages')); + } + + public function testRouteHandlerDependencyInjection() + { + $this->kernel->registerExtension(SpyableTestExtension::class); + $this->kernel->boot(); + + $this->assertInstanceOf(RouteCollection::class, ...SpyableTestExtension::getCalled('routes')); + } + + protected function markTestSuccessful(): void + { + $this->assertTrue(true); + } +} + +class HydeTestExtension extends HydeExtension +{ + // An easy way to assert the handlers are called. + public static array $callCache = []; + + public static function getPageClasses(): array + { + return [ + HydeExtensionTestPage::class, + ]; + } + + public static function discoverFiles(FileCollection $collection): void + { + static::$callCache[] = 'files'; + } + + public static function discoverPages(PageCollection $collection): void + { + static::$callCache[] = 'pages'; + } + + public static function discoverRoutes(RouteCollection $collection): void + { + static::$callCache[] = 'routes'; + } +} + +class SpyableTestExtension extends HydeExtension +{ + private static array $callCache = []; + + public static function discoverFiles(FileCollection $collection): void + { + self::$callCache['files'] = func_get_args(); + } + + public static function discoverPages(PageCollection $collection): void + { + self::$callCache['pages'] = func_get_args(); + } + + public static function discoverRoutes(RouteCollection $collection): void + { + self::$callCache['routes'] = func_get_args(); + } + + public static function getCalled(string $method): array + { + return self::$callCache[$method]; + } +} + +class HydeExtensionTestPage extends HydePage +{ + public function compile(): string + { + return ''; + } +}