Skip to content

Commit

Permalink
Merge pull request #1647 from hydephp/refactor-features-class-to-be-a…
Browse files Browse the repository at this point in the history
…-kernel-singleton

[2.x] Refactor features class to be a kernel singleton
  • Loading branch information
caendesilva authored Apr 8, 2024
2 parents 7b6a7ad + 8dfb7f2 commit fef98d7
Show file tree
Hide file tree
Showing 12 changed files with 337 additions and 167 deletions.
13 changes: 13 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This serves two purposes:

### Added
- Added a new `\Hyde\Framework\Actions\PreBuildTasks\TransferMediaAssets` build task handle media assets transfers for site builds.
- The `\Hyde\Facades\Features` class is no longer marked as internal, and is now thus part of the public API.

### Changed
- **Breaking:** The internals of the navigation system has been rewritten into a new Navigation API. This change is breaking for custom navigation implementations. For more information, see below.
Expand Down Expand Up @@ -83,6 +84,18 @@ want to adapt your code to interact with the new `InMemoryPage`, which is genera

For more information, see https://github.com/hydephp/develop/pull/1498.

## Medium impact

### Features class method renames

The following methods in the `Features` class have been renamed to follow a more consistent naming convention:

- `Features::enabled()` has been renamed to `Features::has()`
- `Features::sitemap()` has been renamed to `Features::hasSitemap()`
- `Features::rss()` has been renamed to `Features::hasRss()`

Note that this class was previously marked as internal in v1, but the change is logged here in case it was used in configuration files or custom code.

## Low impact

### Navigation internal changes
Expand Down
199 changes: 131 additions & 68 deletions packages/framework/src/Facades/Features.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,93 +9,62 @@
use Hyde\Pages\DocumentationPage;
use Hyde\Support\Concerns\Serializable;
use Hyde\Support\Contracts\SerializableContract;
use Hyde\Framework\Concerns\Internal\MockableFeatures;
use Illuminate\Support\Str;

use function get_class_methods;
use function is_array;
use function array_keys;
use function array_filter;
use function extension_loaded;
use function str_starts_with;
use function in_array;
use function collect;
use function substr;
use function count;
use function app;

/**
* Allows features to be enabled and disabled in a simple object-oriented manner.
*
* @internal Until this class is split into a service/manager class, it should not be used outside of Hyde as the API is subject to change.
*
* @todo Split facade logic to service/manager class. (Initial and mock data could be set with boot/set methods)
* Based entirely on Laravel Jetstream (License MIT)
*
* @see https://jetstream.laravel.com/
*/
class Features implements SerializableContract
{
use Serializable;
use MockableFeatures;

/**
* Determine if the given specified is enabled.
* The features that are enabled.
*
* @var array<string, bool>
*/
public static function enabled(string $feature): bool
{
return static::resolveMockedInstance($feature) ?? in_array(
$feature, Config::getArray('hyde.features', static::getDefaultOptions())
);
}

// ================================================
// Determine if a given feature is enabled.
// ================================================

public static function hasHtmlPages(): bool
{
return static::enabled(static::htmlPages());
}
protected array $features = [];

public static function hasBladePages(): bool
{
return static::enabled(static::bladePages());
}

public static function hasMarkdownPages(): bool
{
return static::enabled(static::markdownPages());
}

public static function hasMarkdownPosts(): bool
public function __construct()
{
return static::enabled(static::markdownPosts());
$this->features = $this->boot();
}

public static function hasDocumentationPages(): bool
{
return static::enabled(static::documentationPages());
}

public static function hasDocumentationSearch(): bool
/**
* Determine if the given specified is enabled.
*/
public static function has(string $feature): bool
{
return static::enabled(static::documentationSearch())
&& static::hasDocumentationPages()
&& count(DocumentationPage::files()) > 0;
return in_array($feature, static::enabled());
}

public static function hasDarkmode(): bool
/**
* Get all enabled features.
*
* @return array<string>
*/
public static function enabled(): array
{
return static::enabled(static::darkmode());
return array_keys(array_filter(Hyde::features()->getFeatures()));
}

/**
* Torchlight is by default enabled automatically when an API token
* is set in the .env file but is disabled when running tests.
* Get all features and their status.
*
* @return array<string, bool>
*/
public static function hasTorchlight(): bool
public static function getFeatures(): array
{
return static::enabled(static::torchlight())
&& (Config::getNullableString('torchlight.token') !== null)
&& (app('env') !== 'testing');
return Hyde::features()->toArray();
}

// =================================================
Expand Down Expand Up @@ -142,29 +111,85 @@ public static function torchlight(): string
return 'torchlight';
}

// ================================================
// Determine if a given feature is enabled.
// ================================================

public static function hasHtmlPages(): bool
{
return static::has(static::htmlPages());
}

public static function hasBladePages(): bool
{
return static::has(static::bladePages());
}

public static function hasMarkdownPages(): bool
{
return static::has(static::markdownPages());
}

public static function hasMarkdownPosts(): bool
{
return static::has(static::markdownPosts());
}

public static function hasDocumentationPages(): bool
{
return static::has(static::documentationPages());
}

public static function hasDarkmode(): bool
{
return static::has(static::darkmode());
}

// ====================================================
// Dynamic features that in addition to being enabled
// in the config file, require preconditions to be met.
// ====================================================

/** Can a sitemap be generated? */
public static function sitemap(): bool
/**
* Can a sitemap be generated?
*/
public static function hasSitemap(): bool
{
return static::resolveMockedInstance('sitemap') ?? Hyde::hasSiteUrl()
return Hyde::hasSiteUrl()
&& Config::getBool('hyde.generate_sitemap', true)
&& extension_loaded('simplexml');
}

/** Can an RSS feed be generated? */
public static function rss(): bool
/**
* Can an RSS feed be generated?
*/
public static function hasRss(): bool
{
return static::resolveMockedInstance('rss') ?? Hyde::hasSiteUrl()
return Hyde::hasSiteUrl()
&& static::hasMarkdownPosts()
&& Config::getBool('hyde.rss.enabled', true)
&& extension_loaded('simplexml')
&& count(MarkdownPost::files()) > 0;
}

/**
* Torchlight is by default enabled automatically when an API token
* is set in the .env file but is disabled when running tests.
*/
public static function hasTorchlight(): bool
{
return static::has(static::torchlight())
&& (Config::getNullableString('torchlight.token') !== null)
&& (app('env') !== 'testing');
}

public static function hasDocumentationSearch(): bool
{
return static::has(static::documentationSearch())
&& static::hasDocumentationPages()
&& count(DocumentationPage::files()) > 0;
}

/**
* Get an array representation of the features and their status.
*
Expand All @@ -174,13 +199,10 @@ public static function rss(): bool
*/
public function toArray(): array
{
return collect(get_class_methods(static::class))
->filter(fn (string $method): bool => str_starts_with($method, 'has'))
->mapWithKeys(fn (string $method): array => [
Str::kebab(substr($method, 3)) => static::{$method}(),
])->toArray();
return $this->features;
}

/** @return array<string> */
protected static function getDefaultOptions(): array
{
return [
Expand All @@ -199,4 +221,45 @@ protected static function getDefaultOptions(): array
static::torchlight(),
];
}

protected function boot(): array
{
$options = static::getDefaultOptions();

$enabled = [];

// Set all default features to false
foreach ($options as $feature) {
$enabled[$feature] = false;
}

// Set all features to true if they are enabled in the config file
foreach ($this->getConfiguredFeatures() as $feature) {
if (in_array($feature, $options)) {
$enabled[$feature] = true;
}
}

return $enabled;
}

/** @return array<string> */
protected function getConfiguredFeatures(): array
{
return Config::getArray('hyde.features', static::getDefaultOptions());
}

/**
* @internal This method is not covered by the backward compatibility promise.
*
* @param string|array<string, bool> $feature
*/
public static function mock(string|array $feature, bool $enabled = true): void
{
$features = is_array($feature) ? $feature : [$feature => $enabled];

foreach ($features as $feature => $enabled) {
Hyde::features()->features[$feature] = $enabled;
}
}
}
6 changes: 4 additions & 2 deletions packages/framework/src/Foundation/HydeKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class HydeKernel implements SerializableContract
protected string $outputDirectory = '_site';
protected string $mediaDirectory = '_media';

protected Features $features;
protected Filesystem $filesystem;
protected Hyperlinks $hyperlinks;

Expand All @@ -73,6 +74,7 @@ class HydeKernel implements SerializableContract
public function __construct(?string $basePath = null)
{
$this->setBasePath($basePath ?? getcwd());

$this->filesystem = new Filesystem($this);
$this->hyperlinks = new Hyperlinks($this);

Expand All @@ -86,12 +88,12 @@ public static function version(): string

public function features(): Features
{
return new Features;
return $this->features ??= new Features();
}

public function hasFeature(string $feature): bool
{
return Features::enabled($feature);
return Features::has($feature);
}

/** @inheritDoc */
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ public static function make(): static
$metadata->add($item);
}

if (Features::sitemap()) {
if (Features::hasSitemap()) {
$metadata->add(Meta::link('sitemap', Hyde::url('sitemap.xml'), [
'type' => 'application/xml', 'title' => 'Sitemap',
]));
}

if (Features::rss()) {
if (Features::hasRss()) {
$metadata->add(Meta::link('alternate', Hyde::url(RssFeedGenerator::getFilename()), [
'type' => 'application/rss+xml', 'title' => RssFeedGenerator::getDescription(),
]));
Expand Down
Loading

0 comments on commit fef98d7

Please sign in to comment.