Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions src/Discovery/BundleDiscovery.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ final class BundleDiscovery
/** @var array<string, string> */
private array $metadataHashes = [];

/** @param list<string>|array<string> $packagePrefixes Optional package-name prefixes used to narrow discovery. */
/**
* @param list<string>|array<string> $packagePrefixes Optional package-name prefixes used to narrow discovery.
* @param string|null $cacheDir Resolved kernel cache directory; resolved again from the project when omitted.
*/
public function __construct(
private readonly ActivePackageResolver $resolver,
array $packagePrefixes = [],
private readonly ?string $projectDir = null,
private readonly ?string $environment = null,
private readonly ?string $cacheDir = null,
) {

$this->packagePrefixes = $this->normalizePackagePrefixes($packagePrefixes);
Expand Down Expand Up @@ -548,7 +552,7 @@ private function packageNames(): array
return $this->packageNames;
}

$cache = new KernelPackageManifestCache($this->projectDir, $this->environment, $this->packagePrefixes);
$cache = new KernelPackageManifestCache($this->projectDir, $this->environment, $this->packagePrefixes, $this->cacheDir);
$cachedPackages = $cache->read();

if ($cachedPackages !== null) {
Expand Down
6 changes: 6 additions & 0 deletions src/Discovery/KernelPackageManifestCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public function metadata(): array

/**
* @param list<string> $packagePrefixes
* @param string|null $cacheDir Resolved kernel cache directory; resolved from the project when omitted.
*/
public function __construct(
private readonly ?string $projectDir,
private readonly ?string $environment,
private readonly array $packagePrefixes,
private readonly ?string $cacheDir = null,
) {
}

Expand Down Expand Up @@ -151,6 +153,10 @@ private function cacheFile(): ?string
return null;
}

if ($this->cacheDir !== null && $this->cacheDir !== '') {
return rtrim($this->cacheDir, '/') . '/discovery-packages.json';
}

$environment = $this->environment;

if ($environment === null || $environment === '') {
Expand Down
7 changes: 6 additions & 1 deletion src/Kernel/AbstractKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;

// phpcs:ignore SymPress.Classes.PropertyLimit.TooManyProperties -- The resolver is kept for one boot so the cache path is resolved once per request.
abstract class AbstractKernel implements KernelInterface
{
protected readonly SiteConfig $config;
Expand All @@ -44,6 +45,7 @@ abstract class AbstractKernel implements KernelInterface
protected array $bundles = [];

private ?CoreServiceRegistrar $coreServiceRegistrar = null;
private ?KernelConfigurationResolver $configuration = null;

public function __construct(
protected readonly string $projectDir,
Expand Down Expand Up @@ -76,6 +78,7 @@ public function __clone()
$this->bundleRegistry = null;
$this->bundles = [];
$this->coreServiceRegistrar = null;
$this->configuration = null;
}

/** @return array{project_dir: string, environment: string, debug: bool} */
Expand Down Expand Up @@ -265,6 +268,7 @@ public function discoverBundles(): BundleRegistry
$this->configuration()->packagePrefixes(),
$this->projectDir,
$this->environment,
$this->getCacheDir(),
))->discover();
$this->bundles = $this->bundleMap($this->bundleRegistry);

Expand Down Expand Up @@ -373,7 +377,7 @@ private function containerCacheManager(): ContainerCacheManager

private function configuration(): KernelConfigurationResolver
{
return new KernelConfigurationResolver(
return $this->configuration ??= new KernelConfigurationResolver(
$this->projectDir,
$this->environment,
$this->config,
Expand Down Expand Up @@ -433,6 +437,7 @@ public function shutdown(): void
$this->container = null;
$this->bundleRegistry = null;
$this->bundles = [];
$this->configuration = null;
}

/** @return array<string, BundleInterface> */
Expand Down
24 changes: 18 additions & 6 deletions src/Kernel/KernelConfigurationResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@
use SymPress\Kernel\Bundle\BundleRegistry;
use SymPress\Kernel\SiteConfig;

final readonly class KernelConfigurationResolver
final class KernelConfigurationResolver
{
/** @var list<string>|null */
private ?array $packagePrefixes = null;
private ?string $cacheDir = null;

public function __construct(
private string $projectDir,
private string $environment,
private SiteConfig $config,
private readonly string $projectDir,
private readonly string $environment,
private readonly SiteConfig $config,
) {
}

public function cacheDir(): string
{
return CachePath::resolve($this->projectDir, $this->environment, $this->serverString('APP_CACHE_DIR'));
// Resolving the path walks realpath() and permission checks; a resolver lives for one boot.
return $this->cacheDir ??= CachePath::resolve($this->projectDir, $this->environment, $this->serverString('APP_CACHE_DIR'));
}

public function buildDir(): string
Expand Down Expand Up @@ -65,13 +70,20 @@ public function logDir(): ?string
/** @return list<string> */
public function packagePrefixes(): array
{
if ($this->packagePrefixes !== null) {
return $this->packagePrefixes;
}

$configured = $this->config->get('KERNEL_PACKAGE_PREFIXES', null);

if ($configured === null) {
// The root composer.json is parsed once per resolver, not once per request phase.
$configured = $this->composerKernelPackagePrefixes();
}

return $this->normalizePackagePrefixes($configured);
$this->packagePrefixes = $this->normalizePackagePrefixes($configured);

return $this->packagePrefixes;
}

/** @return list<string> */
Expand Down
14 changes: 14 additions & 0 deletions tests/Discovery/KernelPackageManifestCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ public function testManifestRoundTripsUntilComposerMetadataChanges(): void
self::assertNull($cache->read());
}

public function testExplicitCacheDirectorySkipsResolvingTheProjectCachePath(): void
{
$projectDir = $this->tmpPath('discovery-cache-explicit');
file_put_contents("{$projectDir}/composer.json", '{}');
$cacheDir = "{$projectDir}/resolved/kernel";

$cache = new KernelPackageManifestCache($projectDir, 'production', ['sympress/'], $cacheDir);
$cache->write(['sympress/kernel']);

self::assertFileExists("{$cacheDir}/discovery-packages.json");
self::assertFileDoesNotExist("{$projectDir}/var/cache/production/kernel/discovery-packages.json");
self::assertSame(['sympress/kernel'], $cache->read());
}

public function testUnchangedManifestReadersDoNotInvalidateOpcache(): void
{
$project = $this->tmpPath('discovery-reader-cost');
Expand Down
9 changes: 5 additions & 4 deletions tests/Kernel/RuntimeCacheCompatibilityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,13 @@ public function testCacheDirectoryReadsRealEnvironmentWithServerAndEnvPrecedence
$previous = getenv('APP_CACHE_DIR');
putenv('APP_CACHE_DIR=' . $project . '/external');
try {
$resolver = new KernelConfigurationResolver($project, 'test', new TestSiteConfig('test'));
self::assertSame($project . '/external/test/kernel', $resolver->cacheDir());
// A resolver fixes its cache path for one boot; each precedence step uses a fresh one.
$resolver = static fn (): KernelConfigurationResolver => new KernelConfigurationResolver($project, 'test', new TestSiteConfig('test'));
self::assertSame($project . '/external/test/kernel', $resolver()->cacheDir());
$_ENV['APP_CACHE_DIR'] = $project . '/env';
self::assertSame($project . '/env/test/kernel', $resolver->cacheDir());
self::assertSame($project . '/env/test/kernel', $resolver()->cacheDir());
$_SERVER['APP_CACHE_DIR'] = $project . '/server';
self::assertSame($project . '/server/test/kernel', $resolver->cacheDir());
self::assertSame($project . '/server/test/kernel', $resolver()->cacheDir());
} finally {
unset($_SERVER['APP_CACHE_DIR'], $_ENV['APP_CACHE_DIR']);
putenv($previous === false ? 'APP_CACHE_DIR' : 'APP_CACHE_DIR=' . $previous);
Expand Down
Loading