Skip to content
2 changes: 2 additions & 0 deletions packages/core/src/Commands/DiscoveryGenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Tempest\Container\Container;
use Tempest\Container\GenericContainer;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\DiscoveryConfig;
use Tempest\Core\FrameworkKernel;
use Tempest\Core\Kernel;
use Tempest\Core\Kernel\LoadDiscoveryClasses;
Expand Down Expand Up @@ -59,6 +60,7 @@ public function generateDiscoveryCache(DiscoveryCacheStrategy $strategy, Closure
$loadDiscoveryClasses = new LoadDiscoveryClasses(
kernel: $kernel,
container: $kernel->container,
discoveryConfig: $kernel->container->get(DiscoveryConfig::class),
discoveryCache: $this->discoveryCache,
);

Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/Config/discovery.config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

use Tempest\Core\DiscoveryConfig;

return new DiscoveryConfig();
39 changes: 39 additions & 0 deletions packages/core/src/DiscoveryConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Tempest\Core;

final class DiscoveryConfig
{
private array $skipDiscovery = [];

public function shouldSkip(string $input): bool
{
return $this->skipDiscovery[$input] ?? false;
}

public function skipClasses(string ...$classNames): self
{
foreach ($classNames as $className) {
$this->skipDiscovery[$className] = true;
}

return $this;
}

public function skipPaths(string ...$paths): self
{
foreach ($paths as $path) {
$path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $path);

$realpath = realpath($path);

if ($realpath === false) {
continue;
}

$this->skipDiscovery[$realpath] = true;
}

return $this;
}
}
24 changes: 22 additions & 2 deletions packages/core/src/Kernel/LoadDiscoveryClasses.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Tempest\Cache\DiscoveryCacheStrategy;
use Tempest\Container\Container;
use Tempest\Core\DiscoveryCache;
use Tempest\Core\DiscoveryConfig;
use Tempest\Core\DiscoveryDiscovery;
use Tempest\Core\Kernel;
use Tempest\Discovery\DiscoversPath;
Expand All @@ -29,6 +30,7 @@ final class LoadDiscoveryClasses
public function __construct(
private readonly Kernel $kernel,
private readonly Container $container,
private readonly DiscoveryConfig $discoveryConfig,
private readonly DiscoveryCache $discoveryCache,
) {}

Expand Down Expand Up @@ -100,6 +102,7 @@ private function buildDiscovery(string $discoveryClass): Discovery
/** @var SplFileInfo $file */
foreach ($files as $file) {
$fileName = $file->getFilename();

if ($fileName === '') {
continue;
}
Expand All @@ -112,7 +115,11 @@ private function buildDiscovery(string $discoveryClass): Discovery
continue;
}

$input = $file->getPathname();
$input = $file->getRealPath();

if ($this->shouldSkipBasedOnConfig($input)) {
continue;
}

// We assume that any PHP file that starts with an uppercase letter will be a class
if ($file->getExtension() === 'php' && ucfirst($fileName) === $fileName) {
Expand All @@ -131,14 +138,18 @@ private function buildDiscovery(string $discoveryClass): Discovery
}
}

if ($this->shouldSkipBasedOnConfig($input)) {
continue;
}

if ($input instanceof ClassReflector) {
// If the input is a class, we'll call `discover`
if (! $this->shouldSkipDiscoveryForClass($discovery, $input)) {
$discovery->discover($location, $input);
}
} elseif ($discovery instanceof DiscoversPath) {
// If the input is NOT a class, AND the discovery class can discover paths, we'll call `discoverPath`
$discovery->discoverPath($location, realpath($input));
$discovery->discoverPath($location, $input);
}
}
}
Expand All @@ -160,6 +171,15 @@ private function applyDiscovery(Discovery $discovery): void
$this->appliedDiscovery[$discovery::class] = true;
}

private function shouldSkipBasedOnConfig(ClassReflector|string $input): bool
{
if ($input instanceof ClassReflector) {
$input = $input->getName();
}

return $this->discoveryConfig->shouldSkip($input);
}

/**
* Check whether discovery for a specific class should be skipped based on the #[SkipDiscovery] attribute
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Kernel/LoadDiscoveryLocations.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public function __construct(
public function __invoke(): void
{
$this->kernel->discoveryLocations = [
...$this->kernel->discoveryLocations,
...$this->discoverCorePackages(),
...$this->discoverVendorPackages(),
...$this->discoverAppNamespaces(),
...$this->kernel->discoveryLocations,
];
}

Expand Down
8 changes: 8 additions & 0 deletions tests/Fixtures/Config/discovery.config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

use Tempest\Core\DiscoveryConfig;
use Tests\Tempest\Fixtures\GlobalHiddenDiscovery;

return new DiscoveryConfig()
->skipClasses(GlobalHiddenDiscovery::class)
->skipPaths(__DIR__ . '/../../Fixtures/GlobalHiddenPathDiscovery.php');
24 changes: 24 additions & 0 deletions tests/Fixtures/GlobalHiddenDiscovery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Tests\Tempest\Fixtures;

use Tempest\Discovery\Discovery;
use Tempest\Discovery\DiscoveryLocation;
use Tempest\Discovery\IsDiscovery;
use Tempest\Reflection\ClassReflector;

final class GlobalHiddenDiscovery implements Discovery
{
public static bool $discovered = false;

use IsDiscovery;

public function discover(DiscoveryLocation $location, ClassReflector $class): void
{
}

public function apply(): void
{
self::$discovered = true;
}
}
24 changes: 24 additions & 0 deletions tests/Fixtures/GlobalHiddenPathDiscovery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Tests\Tempest\Fixtures;

use Tempest\Discovery\Discovery;
use Tempest\Discovery\DiscoveryLocation;
use Tempest\Discovery\IsDiscovery;
use Tempest\Reflection\ClassReflector;

final class GlobalHiddenPathDiscovery implements Discovery
{
public static bool $discovered = false;

use IsDiscovery;

public function discover(DiscoveryLocation $location, ClassReflector $class): void
{
}

public function apply(): void
{
self::$discovered = true;
}
}
38 changes: 14 additions & 24 deletions tests/Integration/Core/LoadDiscoveryClassesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
namespace Tests\Tempest\Integration\Core;

use PHPUnit\Framework\Attributes\Test;
use Tempest\Core\DiscoveryConfig;
use Tempest\Database\DatabaseMigration;
use Tempest\Database\MigrationDiscovery;
use Tempest\Database\Migrations\RunnableMigrations;
use Tempest\Discovery\DiscoveryLocation;
use Tests\Tempest\Fixtures\Discovery\HiddenDatabaseMigration;
use Tests\Tempest\Fixtures\Discovery\HiddenMigratableDatabaseMigration;
use Tests\Tempest\Fixtures\GlobalHiddenDiscovery;
use Tests\Tempest\Fixtures\GlobalHiddenPathDiscovery;
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;

use function Tempest\get;
Expand All @@ -23,37 +24,26 @@ final class LoadDiscoveryClassesTest extends FrameworkIntegrationTestCase
#[Test]
public function do_not_discover(): void
{
$this->kernel->discoveryClasses = [
MigrationDiscovery::class,
];

$this->kernel->discoveryLocations = [
new DiscoveryLocation(
'Tests\Tempest\Fixtures',
__DIR__ . '../../Fixtures/Discovery',
),
];

$migrations = get(RunnableMigrations::class);

$this->assertNotContains(HiddenDatabaseMigration::class, $migrations);
}

#[Test]
public function do_not_discover_except(): void
public function do_not_discover_global_class(): void
{
$this->kernel->discoveryClasses = [
MigrationDiscovery::class,
// TODO: update tests to add `PublishDiscovery` when it's merged
];
$this->assertFalse(GlobalHiddenDiscovery::$discovered);
}

$this->kernel->discoveryLocations = [
new DiscoveryLocation(
'Tests\Tempest\Fixtures',
__DIR__ . '../../Fixtures/Discovery',
),
];
#[Test]
public function do_not_discover_global_path(): void
{
$this->assertFalse(GlobalHiddenPathDiscovery::$discovered);
}

#[Test]
public function do_not_discover_except(): void
{
$migrations = get(RunnableMigrations::class);

$foundMigrations = array_filter(
Expand Down
Loading