Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a simple framework for Hyde extensions #828

Merged
merged 33 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
582266e
Create HydeExtension.php
caendesilva Jan 11, 2023
10031ae
Make class abstract
caendesilva Jan 11, 2023
5304588
Add static void method stubs for discovery collections
caendesilva Jan 11, 2023
c1e7aeb
Add getter to provide page classes
caendesilva Jan 11, 2023
5882718
Create kernel $extensions array
caendesilva Jan 11, 2023
e89d91a
Add kernel extension class getter and setter
caendesilva Jan 11, 2023
f6e78d8
Add rich type annotations
caendesilva Jan 11, 2023
6909cab
Remove suffix from parameter name
caendesilva Jan 11, 2023
cb008f4
Add return type annotation
caendesilva Jan 11, 2023
1129e71
Add dependency injection for the foundation collection callbacks
caendesilva Jan 11, 2023
f0c45d9
Call extension handlers at the end of collection discovery
caendesilva Jan 11, 2023
a661279
Add loop type annotations
caendesilva Jan 11, 2023
d265718
Merge branch 'master' into create-extension-framework
caendesilva Jan 11, 2023
da43759
Move base class HydeExtension into Concerns namespace
caendesilva Jan 11, 2023
c23a234
Mark registerPageClass as deprecation candidate
caendesilva Jan 11, 2023
5a7f2cd
Apply fixes from StyleCI
StyleCIBot Jan 11, 2023
1f3b168
Create HydeExtensionTest.php
caendesilva Jan 11, 2023
becf067
Create a test class
caendesilva Jan 11, 2023
1f7f834
Add additional testing classes
caendesilva Jan 11, 2023
8af4103
Rename the test
caendesilva Jan 11, 2023
02482ee
Add additional coverage tags
caendesilva Jan 11, 2023
08af723
Set up kernel accessor
caendesilva Jan 11, 2023
65fcd58
Test registration
caendesilva Jan 11, 2023
d1c82c9
Test handler methods are called by discovery
caendesilva Jan 11, 2023
bb11670
Overengineer testing system for asserting called input
caendesilva Jan 11, 2023
ca3fcd9
Apply fixes from StyleCI
StyleCIBot Jan 11, 2023
81f781c
Test base class
caendesilva Jan 11, 2023
5a1cfe1
Extract semantic helper
caendesilva Jan 11, 2023
ce204e7
Apply fixes from StyleCI
StyleCIBot Jan 11, 2023
f9e63c3
Document the extension class
caendesilva Jan 11, 2023
03dab59
Apply fixes from StyleCI
StyleCIBot Jan 11, 2023
0528c62
Link to the docs
caendesilva Jan 11, 2023
be6f30b
Apply fixes from StyleCI
StyleCIBot Jan 11, 2023
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
79 changes: 79 additions & 0 deletions packages/framework/src/Foundation/Concerns/HydeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace Hyde\Foundation\Concerns;

use Hyde\Foundation\FileCollection;
use Hyde\Foundation\PageCollection;
use Hyde\Foundation\RouteCollection;

/**
* When creating a HydePHP extension, you should create a class that extends this one.
*
* After registering your implementation with the HydeKernel (usually in a service provider),
* Hyde will be able to use the information within to integrate your plugin, and to allow you to
* hook into various parts of the internal application lifecycle, and through that, all aspects of Hyde.
*
* Before creating your extension, it will certainly be helpful if you first become familiar
* with the basic internal architecture of HydePHP, as well as how the auto-discovery system functions.
*
* @link https://hydephp.com/docs/master/architecture-concepts
*
* It's important that your class is registered before the HydeKernel boots.
* An excellent place for this is the 'register' method of your extensions service provider,
* where you call the 'registerExtension' method of the HydeKernel singleton instance,
* which you can access via the Hyde\Hyde facade, or via the service container.
*
* @example ```php $this->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<class-string<\Hyde\Pages\Concerns\HydePage>>
*/
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
{
//
}
}
16 changes: 16 additions & 0 deletions packages/framework/src/Foundation/Concerns/ManagesHydeKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<class-string<\Hyde\Foundation\Concerns\HydeExtension>> */
public function getRegisteredExtensions(): array
{
return $this->extensions;
}
}
5 changes: 5 additions & 0 deletions packages/framework/src/Foundation/FileCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions packages/framework/src/Foundation/HydeKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '')
Expand Down
5 changes: 5 additions & 0 deletions packages/framework/src/Foundation/PageCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
5 changes: 5 additions & 0 deletions packages/framework/src/Foundation/RouteCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
158 changes: 158 additions & 0 deletions packages/framework/tests/Feature/HydeExtensionFeatureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Testing\Feature;

use function func_get_args;
use Hyde\Foundation\Concerns\HydeExtension;
use Hyde\Foundation\FileCollection;
use Hyde\Foundation\HydeKernel;
use Hyde\Foundation\PageCollection;
use Hyde\Foundation\RouteCollection;
use Hyde\Hyde;
use Hyde\Pages\Concerns\HydePage;
use Hyde\Testing\TestCase;

/**
* @covers \Hyde\Foundation\Concerns\HydeExtension
* @covers \Hyde\Foundation\Concerns\ManagesHydeKernel
* @covers \Hyde\Foundation\HydeKernel
* @covers \Hyde\Foundation\FileCollection
* @covers \Hyde\Foundation\PageCollection
* @covers \Hyde\Foundation\RouteCollection
*/
class HydeExtensionFeatureTest extends TestCase
{
protected HydeKernel $kernel;

protected function setUp(): void
{
parent::setUp();

$this->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 '';
}
}