diff --git a/src/Tempest/Cache/src/ProjectCache.php b/src/Tempest/Cache/src/ProjectCache.php
index 9900183d8..7ea6842d6 100644
--- a/src/Tempest/Cache/src/ProjectCache.php
+++ b/src/Tempest/Cache/src/ProjectCache.php
@@ -18,7 +18,7 @@ public function __construct(
private readonly CacheConfig $cacheConfig,
) {
$this->pool = $this->cacheConfig->projectCachePool ?? new FilesystemAdapter(
- directory: path($this->cacheConfig->directory, 'project'),
+ directory: path($this->cacheConfig->directory, 'project')->toString(),
);
}
diff --git a/src/Tempest/Console/src/ConsoleApplication.php b/src/Tempest/Console/src/ConsoleApplication.php
index 315c4ce4a..3848134ff 100644
--- a/src/Tempest/Console/src/ConsoleApplication.php
+++ b/src/Tempest/Console/src/ConsoleApplication.php
@@ -13,7 +13,7 @@
use Tempest\Core\Tempest;
use Tempest\Log\Channels\AppendLogChannel;
use Tempest\Log\LogConfig;
-use Tempest\Support\PathHelper;
+use function Tempest\path;
use Throwable;
final readonly class ConsoleApplication implements Application
@@ -45,8 +45,8 @@ public static function boot(
$logConfig->debugLogPath === null
&& $logConfig->channels === []
) {
- $logConfig->debugLogPath = PathHelper::make($container->get(Kernel::class)->root, '/log/debug.log');
- $logConfig->channels[] = new AppendLogChannel(PathHelper::make($container->get(Kernel::class)->root, '/log/tempest.log'));
+ $logConfig->debugLogPath = path($container->get(Kernel::class)->root, '/log/debug.log')->toString();
+ $logConfig->channels[] = new AppendLogChannel(path($container->get(Kernel::class)->root, '/log/tempest.log')->toString());
}
return $application;
diff --git a/src/Tempest/Console/src/Initializers/LogOutputBufferInitializer.php b/src/Tempest/Console/src/Initializers/LogOutputBufferInitializer.php
index 52a4d5f0a..a41128820 100644
--- a/src/Tempest/Console/src/Initializers/LogOutputBufferInitializer.php
+++ b/src/Tempest/Console/src/Initializers/LogOutputBufferInitializer.php
@@ -10,7 +10,7 @@
use Tempest\Container\Initializer;
use Tempest\Container\Singleton;
use Tempest\Core\Kernel;
-use Tempest\Support\PathHelper;
+use function Tempest\path;
final readonly class LogOutputBufferInitializer implements Initializer
{
@@ -20,7 +20,7 @@ public function initialize(Container $container): LogOutputBuffer
$consoleConfig = $container->get(ConsoleConfig::class);
$kernel = $container->get(Kernel::class);
- $path = $consoleConfig->logPath ?? PathHelper::make($kernel->root, 'console.log');
+ $path = $consoleConfig->logPath ?? path($kernel->root, 'console.log')->toString();
return new LogOutputBuffer($path);
}
diff --git a/src/Tempest/Core/composer.json b/src/Tempest/Core/composer.json
index d0283ac38..837d8347c 100644
--- a/src/Tempest/Core/composer.json
+++ b/src/Tempest/Core/composer.json
@@ -6,6 +6,7 @@
"require": {
"php": "^8.3",
"tempest/container": "dev-main",
+ "tempest/support": "dev-main",
"vlucas/phpdotenv": "^5.6",
"filp/whoops": "^2.15"
},
diff --git a/src/Tempest/Core/src/Composer.php b/src/Tempest/Core/src/Composer.php
index a9910ce2c..e504e6a2a 100644
--- a/src/Tempest/Core/src/Composer.php
+++ b/src/Tempest/Core/src/Composer.php
@@ -4,8 +4,8 @@
namespace Tempest\Core;
+use function Tempest\path;
use function Tempest\Support\arr;
-use Tempest\Support\PathHelper;
final class Composer
{
@@ -19,7 +19,7 @@ final class Composer
public function __construct(
string $root,
) {
- $composerFilePath = PathHelper::make($root, 'composer.json');
+ $composerFilePath = path($root, 'composer.json')->toString();
$this->composer = $this->loadComposerFile($composerFilePath);
$this->namespaces = arr($this->composer)
diff --git a/src/Tempest/Core/src/DiscoveryCache.php b/src/Tempest/Core/src/DiscoveryCache.php
index 2c5ee6df5..10e3cfc2c 100644
--- a/src/Tempest/Core/src/DiscoveryCache.php
+++ b/src/Tempest/Core/src/DiscoveryCache.php
@@ -27,7 +27,7 @@ public function __construct(
?CacheItemPoolInterface $pool = null,
) {
$this->pool = $pool ?? new FilesystemAdapter(
- directory: path($this->cacheConfig->directory, 'discovery'),
+ directory: path($this->cacheConfig->directory, 'discovery')->toString(),
);
}
diff --git a/src/Tempest/Core/src/Kernel/LoadDiscoveryLocations.php b/src/Tempest/Core/src/Kernel/LoadDiscoveryLocations.php
index 9626d2833..067fe8af5 100644
--- a/src/Tempest/Core/src/Kernel/LoadDiscoveryLocations.php
+++ b/src/Tempest/Core/src/Kernel/LoadDiscoveryLocations.php
@@ -8,7 +8,7 @@
use Tempest\Core\DiscoveryException;
use Tempest\Core\DiscoveryLocation;
use Tempest\Core\Kernel;
-use Tempest\Support\PathHelper;
+use function Tempest\path;
/** @internal */
final readonly class LoadDiscoveryLocations
@@ -35,8 +35,8 @@ public function __invoke(): void
*/
private function discoverCorePackages(): array
{
- $composerPath = PathHelper::make($this->kernel->root, 'vendor/composer');
- $installed = $this->loadJsonFile(PathHelper::make($composerPath, 'installed.json'));
+ $composerPath = path($this->kernel->root, 'vendor/composer');
+ $installed = $this->loadJsonFile(path($composerPath, 'installed.json')->toString());
$packages = $installed['packages'] ?? [];
$discoveredLocations = [];
@@ -49,12 +49,12 @@ private function discoverCorePackages(): array
continue;
}
- $packagePath = PathHelper::make($composerPath, $package['install-path'] ?? '');
+ $packagePath = path($composerPath, $package['install-path'] ?? '');
foreach ($package['autoload']['psr-4'] as $namespace => $namespacePath) {
- $namespacePath = PathHelper::make($packagePath, $namespacePath);
+ $namespacePath = path($packagePath, $namespacePath);
- $discoveredLocations[] = new DiscoveryLocation($namespace, $namespacePath);
+ $discoveredLocations[] = new DiscoveryLocation($namespace, $namespacePath->toString());
}
}
@@ -69,9 +69,9 @@ private function discoverAppNamespaces(): array
$discoveredLocations = [];
foreach ($this->composer->namespaces as $namespace) {
- $path = PathHelper::make($this->kernel->root, $namespace->path);
+ $path = path($this->kernel->root, $namespace->path);
- $discoveredLocations[] = new DiscoveryLocation($namespace->namespace, $path);
+ $discoveredLocations[] = new DiscoveryLocation($namespace->namespace, $path->toString());
}
return $discoveredLocations;
@@ -82,8 +82,8 @@ private function discoverAppNamespaces(): array
*/
private function discoverVendorPackages(): array
{
- $composerPath = PathHelper::make($this->kernel->root, 'vendor/composer');
- $installed = $this->loadJsonFile(PathHelper::make($composerPath, 'installed.json'));
+ $composerPath = path($this->kernel->root, 'vendor/composer');
+ $installed = $this->loadJsonFile(path($composerPath, 'installed.json')->toString());
$packages = $installed['packages'] ?? [];
$discoveredLocations = [];
@@ -96,7 +96,7 @@ private function discoverVendorPackages(): array
continue;
}
- $packagePath = PathHelper::make($composerPath, $package['install-path'] ?? '');
+ $packagePath = path($composerPath, $package['install-path'] ?? '');
$requiresTempest = isset($package['require']['tempest/framework']) || isset($package['require']['tempest/core']);
$hasPsr4Namespaces = isset($package['autoload']['psr-4']);
@@ -105,9 +105,9 @@ private function discoverVendorPackages(): array
}
foreach ($package['autoload']['psr-4'] as $namespace => $namespacePath) {
- $path = PathHelper::make($packagePath, $namespacePath);
+ $path = path($packagePath, $namespacePath);
- $discoveredLocations[] = new DiscoveryLocation($namespace, $path);
+ $discoveredLocations[] = new DiscoveryLocation($namespace, $path->toString());
}
}
diff --git a/src/Tempest/Core/src/PublishesFiles.php b/src/Tempest/Core/src/PublishesFiles.php
index d5b6db3de..1a809f007 100644
--- a/src/Tempest/Core/src/PublishesFiles.php
+++ b/src/Tempest/Core/src/PublishesFiles.php
@@ -13,7 +13,8 @@
use Tempest\Generation\Exceptions\FileGenerationAbortedException;
use Tempest\Generation\Exceptions\FileGenerationFailedException;
use Tempest\Generation\StubFileGenerator;
-use Tempest\Support\PathHelper;
+use function Tempest\path;
+use Tempest\Support\NamespaceHelper;
use function Tempest\Support\str;
use Tempest\Validation\Rules\EndsWith;
use Tempest\Validation\Rules\NotEmpty;
@@ -133,15 +134,15 @@ public function publishImports(): void
public function getSuggestedPath(string $className, ?string $pathPrefix = null, ?string $classSuffix = null): string
{
// Separate input path and classname
- $inputClassName = PathHelper::toClassName($className);
- $inputPath = str(PathHelper::make($className))->replaceLast($inputClassName, '')->toString();
+ $inputClassName = NamespaceHelper::toClassName($className);
+ $inputPath = str(path($className))->replaceLast($inputClassName, '')->toString();
$className = str($inputClassName)
->pascal()
->finish($classSuffix ?? '')
->toString();
// Prepare the suggested path from the project namespace
- return str(PathHelper::make(
+ return str(path(
$this->composer->mainNamespace->path,
$pathPrefix ?? '',
$inputPath,
@@ -158,7 +159,7 @@ public function getSuggestedPath(string $className, ?string $pathPrefix = null,
*/
public function promptTargetPath(string $suggestedPath): string
{
- $className = PathHelper::toClassName($suggestedPath);
+ $className = NamespaceHelper::toClassName($suggestedPath);
return $this->console->ask(
question: sprintf('Where do you want to save the file "%s"?', $className),
diff --git a/src/Tempest/Core/src/functions.php b/src/Tempest/Core/src/functions.php
index 35402a5f8..9279d98ce 100644
--- a/src/Tempest/Core/src/functions.php
+++ b/src/Tempest/Core/src/functions.php
@@ -8,28 +8,15 @@
use Tempest\Core\Composer;
use Tempest\Core\DeferredTasks;
use Tempest\Core\Kernel;
+ use function Tempest\Support\path;
use function Tempest\Support\str;
- /**
- * Creates and sanitizes a file system path from the given `$parts`. The resulting path is not checked against the file system.
- */
- function path(string ...$parts): string
- {
- $path = implode('/', $parts);
-
- return str_replace(
- ['///', '//', '\\', '\\\\'],
- '/',
- $path,
- );
- }
-
/**
* Creates a path scoped within the root of the project
*/
function root_path(string ...$parts): string
{
- return path(realpath(get(Kernel::class)->root), ...$parts);
+ return path(realpath(get(Kernel::class)->root), ...$parts)->toString();
}
/**
@@ -39,7 +26,7 @@ function src_path(string ...$parts): string
{
$composer = get(Composer::class);
- return path($composer->mainNamespace->path, ...$parts);
+ return path($composer->mainNamespace->path, ...$parts)->toString();
}
/**
diff --git a/src/Tempest/Framework/Testing/InstallerTester.php b/src/Tempest/Framework/Testing/InstallerTester.php
index a736e9498..e67831d3d 100644
--- a/src/Tempest/Framework/Testing/InstallerTester.php
+++ b/src/Tempest/Framework/Testing/InstallerTester.php
@@ -48,7 +48,7 @@ public function setRoot(string $root): self
public function path(string $path): string
{
- return path($this->root, $path);
+ return path($this->root, $path)->toString();
}
public function put(string $path, string $contents): self
diff --git a/src/Tempest/Generation/src/StubFileGenerator.php b/src/Tempest/Generation/src/StubFileGenerator.php
index 7f067d6d3..73fdd37a7 100644
--- a/src/Tempest/Generation/src/StubFileGenerator.php
+++ b/src/Tempest/Generation/src/StubFileGenerator.php
@@ -9,7 +9,8 @@
use Tempest\Generation\Enums\StubFileType;
use Tempest\Generation\Exceptions\FileGenerationAbortedException;
use Tempest\Generation\Exceptions\FileGenerationFailedException;
-use Tempest\Support\PathHelper;
+use function Tempest\path;
+use Tempest\Support\NamespaceHelper;
use function Tempest\Support\str;
use Tempest\Support\StringHelper;
use Throwable;
@@ -51,8 +52,8 @@ public function generateClassFile(
$this->prepareFilesystem($targetPath);
// Transform stub to class
- $namespace = PathHelper::toMainNamespace($targetPath);
- $classname = PathHelper::toClassName($targetPath);
+ $namespace = NamespaceHelper::toMainNamespace($targetPath);
+ $classname = NamespaceHelper::toClassName($targetPath);
$classManipulator = (new ClassManipulator($stubFile->filePath))
->setNamespace($namespace)
->setClassName($classname);
@@ -68,8 +69,8 @@ public function generateClassFile(
// Run all manipulations
$classManipulator = array_reduce(
array: $manipulations,
- initial: $classManipulator,
- callback: fn (ClassManipulator $manipulator, Closure $manipulation) => $manipulation($manipulator)
+ callback: fn (ClassManipulator $manipulator, Closure $manipulation) => $manipulation($manipulator),
+ initial: $classManipulator
);
if (file_exists($targetPath) && $shouldOverride) {
diff --git a/src/Tempest/Http/src/HttpApplication.php b/src/Tempest/Http/src/HttpApplication.php
index 4b6d1c581..99fe65e25 100644
--- a/src/Tempest/Http/src/HttpApplication.php
+++ b/src/Tempest/Http/src/HttpApplication.php
@@ -14,7 +14,7 @@
use Tempest\Http\Session\Session;
use Tempest\Log\Channels\AppendLogChannel;
use Tempest\Log\LogConfig;
-use Tempest\Support\PathHelper;
+use function Tempest\path;
use Throwable;
#[Singleton]
@@ -41,9 +41,9 @@ public static function boot(
&& $logConfig->serverLogPath === null
&& $logConfig->channels === []
) {
- $logConfig->debugLogPath = PathHelper::make($container->get(Kernel::class)->root, '/log/debug.log');
+ $logConfig->debugLogPath = path($container->get(Kernel::class)->root, '/log/debug.log')->toString();
$logConfig->serverLogPath = env('SERVER_LOG');
- $logConfig->channels[] = new AppendLogChannel(PathHelper::make($root, '/log/tempest.log'));
+ $logConfig->channels[] = new AppendLogChannel(path($root, '/log/tempest.log')->toString());
}
return $application;
diff --git a/src/Tempest/Http/src/Session/Managers/FileSessionManager.php b/src/Tempest/Http/src/Session/Managers/FileSessionManager.php
index 0d46c5309..da9cc5df7 100644
--- a/src/Tempest/Http/src/Session/Managers/FileSessionManager.php
+++ b/src/Tempest/Http/src/Session/Managers/FileSessionManager.php
@@ -68,7 +68,7 @@ public function isValid(SessionId $id): bool
private function getPath(SessionId $id): string
{
- return path($this->sessionConfig->path, (string)$id);
+ return path($this->sessionConfig->path, (string)$id)->toString();
}
private function resolve(SessionId $id): ?Session
@@ -129,7 +129,7 @@ private function persist(SessionId $id, ?array $data = null): Session
public function cleanup(): void
{
- $sessionFiles = glob(path($this->sessionConfig->path, '/*'));
+ $sessionFiles = glob(path($this->sessionConfig->path, '/*')->toString());
foreach ($sessionFiles as $sessionFile) {
$id = new SessionId(pathinfo($sessionFile, PATHINFO_FILENAME));
diff --git a/src/Tempest/Http/src/Static/StaticGenerateCommand.php b/src/Tempest/Http/src/Static/StaticGenerateCommand.php
index f81d4e927..ae0e4d7ae 100644
--- a/src/Tempest/Http/src/Static/StaticGenerateCommand.php
+++ b/src/Tempest/Http/src/Static/StaticGenerateCommand.php
@@ -84,13 +84,13 @@ public function __invoke(): void
continue;
}
- $directory = pathinfo($file, PATHINFO_DIRNAME);
+ $directory = $file->dirname();
if (! is_dir($directory)) {
mkdir($directory, recursive: true);
}
- file_put_contents($file, $content);
+ file_put_contents($file->path(), $content);
$this->writeln("- {$uri} > {$file}");
} catch (Throwable $e) {
diff --git a/src/Tempest/Support/src/NamespaceHelper.php b/src/Tempest/Support/src/NamespaceHelper.php
new file mode 100644
index 000000000..572a8c536
--- /dev/null
+++ b/src/Tempest/Support/src/NamespaceHelper.php
@@ -0,0 +1,90 @@
+replaceEnd('\\', '');
+
+ return arr(explode('\\', (string) $path))
+ ->map(fn (string $segment) => (string) str($segment)->pascal())
+ ->implode('\\')
+ ->toString();
+ }
+
+ public static function toMainNamespace(string $path): string
+ {
+ return self::toNamespace(
+ src_namespace() . '/' . str($path)
+ ->replaceStart(src_path(), '')
+ ->trim('/')
+ ->toString()
+ );
+ }
+
+ public static function toRegisteredNamespace(string $path): string
+ {
+ $composer = get(Composer::class);
+ $kernel = get(Kernel::class);
+
+ $relativePath = self::prepareStringForNamespace($path, $kernel->root)
+ ->replaceEnd('\\', '')
+ ->replace('\\', '/')
+ ->finish('/');
+
+ foreach ($composer->namespaces as $namespace) {
+ if ($relativePath->startsWith($namespace->path)) {
+ return (string) $relativePath
+ ->replace($namespace->path, $namespace->namespace)
+ ->replace(['/', '//'], '\\')
+ ->replaceEnd('.php', '')
+ ->replaceEnd('\\', '');
+ }
+ }
+
+ throw new Exception(sprintf('No registered namespace matches the specified path [%s].', $path));
+ }
+
+ /**
+ * Convert a path to a class name.
+ *
+ * @param string $path The path to convert.
+ */
+ public static function toClassName(string $path): string
+ {
+ return str($path)
+ ->replace(['/', '\\'], '/')
+ ->replaceEnd('/', '')
+ ->replaceEnd('.php', '')
+ ->afterLast('/')
+ ->classBasename()
+ ->toString();
+ }
+
+ private static function prepareStringForNamespace(string $path, string $root = ''): StringHelper
+ {
+ $normalized = str($path)
+ ->replaceStart($root, '')
+ ->replaceStart('/', '')
+ ->replace(['/', '//'], '\\');
+
+ // If the path is a to a PHP file, we exclude the file name. Otherwise,
+ // it's a path to a directory, which should be included in the namespace.
+ if ($normalized->endsWith('.php')) {
+ return $normalized->beforeLast(['/', '\\']);
+ }
+
+ return $normalized;
+ }
+}
diff --git a/src/Tempest/Support/src/PathHelper.php b/src/Tempest/Support/src/PathHelper.php
index 428710184..dae12491b 100644
--- a/src/Tempest/Support/src/PathHelper.php
+++ b/src/Tempest/Support/src/PathHelper.php
@@ -4,20 +4,19 @@
namespace Tempest\Support;
-use Exception;
-use Tempest\Core\Composer;
-use Tempest\Core\Kernel;
-use function Tempest\get;
-use function Tempest\src_namespace;
-use function Tempest\src_path;
-
-final readonly class PathHelper
+use Stringable;
+
+final readonly class PathHelper implements Stringable
{
- /**
- * Returns a valid path from the specified portions.
- */
- public static function make(string ...$paths): string
+ private string $path;
+
+ public function __construct(Stringable|string ...$paths)
{
+ $paths = array_map(
+ fn (self|string $path) => (string)$path,
+ $paths,
+ );
+
// Split paths items on forward and backward slashes
$parts = array_reduce($paths, fn (array $carry, string $part) => [...$carry, ...explode('/', $part)], []);
$parts = array_reduce($parts, fn (array $carry, string $part) => [...$carry, ...explode('\\', $part)], []);
@@ -31,91 +30,85 @@ public static function make(string ...$paths): string
// Add / if first entry starts with forward- or backward slash
$firstEntry = $paths[0];
+
if (str_starts_with($firstEntry, '/') || str_starts_with($firstEntry, '\\')) {
$path = '/' . $path;
}
// Add / if last entry ends with forward- or backward slash
$lastEntry = $paths[count($paths) - 1];
+
if ((count($paths) > 1 || strlen($lastEntry) > 1) && (str_ends_with($lastEntry, '/') || str_ends_with($lastEntry, '\\'))) {
$path .= '/';
}
- return $path;
+ $this->path = $path;
}
- private static function prepareStringForNamespace(string $path, string $root = ''): StringHelper
+ public function toString(): string
{
- $normalized = str($path)
- ->replaceStart($root, '')
- ->replaceStart('/', '')
- ->replace(['/', '//'], '\\');
-
- // If the path is a to a PHP file, we exclude the file name. Otherwise,
- // it's a path to a directory, which should be included in the namespace.
- if ($normalized->endsWith('.php')) {
- return $normalized->beforeLast(['/', '\\']);
- }
+ return $this->path;
+ }
- return $normalized;
+ public function info(int $flags = PATHINFO_ALL): string|array
+ {
+ return pathinfo($this->path, $flags);
}
- public static function toNamespace(string $path, string $root = ''): string
+ public function path(): string
{
- $path = static::prepareStringForNamespace($path, $root)->replaceEnd('\\', '');
+ return $this->path;
+ }
- return arr(explode('\\', (string) $path))
- ->map(fn (string $segment) => (string) str($segment)->pascal())
- ->implode('\\')
- ->toString();
+ public function dirname(): string
+ {
+ return $this->info(PATHINFO_DIRNAME);
}
- public static function toMainNamespace(string $path): string
+ public function filename(): string
{
- return self::toNamespace(
- src_namespace() . '/' . str($path)
- ->replaceStart(src_path(), '')
- ->trim('/')
- ->toString()
+ return $this->info(PATHINFO_FILENAME);
+ }
+
+ public function basename(): string
+ {
+ return $this->info(PATHINFO_BASENAME);
+ }
+
+ public function extension(): string
+ {
+ return $this->info(PATHINFO_EXTENSION);
+ }
+
+ public function glob(string $pattern): ArrayHelper
+ {
+ return arr(
+ glob((new self($this->path, $pattern))->toString()),
);
}
- public static function toRegisteredNamespace(string $path): string
+ public function isDirectory(): bool
{
- $composer = get(Composer::class);
- $kernel = get(Kernel::class);
-
- $relativePath = static::prepareStringForNamespace($path, $kernel->root)
- ->replaceEnd('\\', '')
- ->replace('\\', '/')
- ->finish('/');
-
- foreach ($composer->namespaces as $namespace) {
- if ($relativePath->startsWith($namespace->path)) {
- return (string) $relativePath
- ->replace($namespace->path, $namespace->namespace)
- ->replace(['/', '//'], '\\')
- ->replaceEnd('.php', '')
- ->replaceEnd('\\', '');
- }
- }
+ return is_dir($this->path);
+ }
- throw new Exception(sprintf('No registered namespace matches the specified path [%s].', $path));
+ public function isFile(): bool
+ {
+ return is_file($this->path);
+ }
+
+ public function exists(): bool
+ {
+ return file_exists($this->path);
+ }
+
+ public function equals(Stringable $other): bool
+ {
+ return $this->path === (string)$other;
}
- /**
- * Convert a path to a class name.
- *
- * @param string $path The path to convert.
- */
- public static function toClassName(string $path): string
+ public function __toString(): string
{
- return str($path)
- ->replace(['/', '\\'], '/')
- ->replaceEnd('/', '')
- ->replaceEnd('.php', '')
- ->afterLast('/')
- ->classBasename()
- ->toString();
+ return $this->path;
}
}
diff --git a/src/Tempest/Support/src/StringHelper.php b/src/Tempest/Support/src/StringHelper.php
index 0193b6522..d671b4cba 100644
--- a/src/Tempest/Support/src/StringHelper.php
+++ b/src/Tempest/Support/src/StringHelper.php
@@ -16,9 +16,9 @@
{
private string $string;
- public function __construct(?string $string = '')
+ public function __construct(Stringable|string|null $string = '')
{
- $this->string = $string ?? '';
+ $this->string = (string) ($string ?? '');
}
/**
diff --git a/src/Tempest/Support/src/functions.php b/src/Tempest/Support/src/functions.php
index 43cdd15b0..49c65a370 100644
--- a/src/Tempest/Support/src/functions.php
+++ b/src/Tempest/Support/src/functions.php
@@ -3,10 +3,13 @@
declare(strict_types=1);
namespace Tempest\Support {
+
+ use Stringable;
+
/**
* Creates an instance of {@see StringHelper} using the given `$string`.
*/
- function str(?string $string = ''): StringHelper
+ function str(Stringable|string|null $string = ''): StringHelper
{
return new StringHelper($string);
}
@@ -18,4 +21,12 @@ function arr(mixed $input = []): ArrayHelper
{
return new ArrayHelper($input);
}
+
+ /**
+ * Creates and sanitizes a file system path from the given `$parts`. The resulting path is not checked against the file system.
+ */
+ function path(Stringable|string ...$parts): PathHelper
+ {
+ return new PathHelper(...$parts);
+ }
}
diff --git a/src/Tempest/Support/tests/PathHelperTest.php b/src/Tempest/Support/tests/PathHelperTest.php
index ba2046804..cb9580209 100644
--- a/src/Tempest/Support/tests/PathHelperTest.php
+++ b/src/Tempest/Support/tests/PathHelperTest.php
@@ -8,7 +8,8 @@
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
-use Tempest\Support\PathHelper;
+use function Tempest\path;
+use Tempest\Support\NamespaceHelper;
/**
* @internal
@@ -19,7 +20,7 @@ final class PathHelperTest extends TestCase
public function test_make(array $paths, string $expected): void
{
// Act
- $output = PathHelper::make(...$paths);
+ $output = path(...$paths)->toString();
// Assert
$this->assertSame($expected, $output);
@@ -118,8 +119,8 @@ public static function paths(): Generator
public function toClassName(string $path, string $expected): void
{
$this->assertSame(
- actual: PathHelper::toClassName($path),
expected: $expected,
+ actual: NamespaceHelper::toClassName($path),
);
}
diff --git a/src/Tempest/View/src/Renderers/TempestViewCompiler.php b/src/Tempest/View/src/Renderers/TempestViewCompiler.php
index b5c0804ac..ea1b4cb28 100644
--- a/src/Tempest/View/src/Renderers/TempestViewCompiler.php
+++ b/src/Tempest/View/src/Renderers/TempestViewCompiler.php
@@ -67,7 +67,7 @@ private function retrieveTemplate(string $path): string
$searchPath = $path;
while (! file_exists($searchPath) && $location = current($discoveryLocations)) {
- $searchPath = path($location->path, $path);
+ $searchPath = path($location->path, $path)->toString();
next($discoveryLocations);
}
diff --git a/src/Tempest/View/src/ViewCache.php b/src/Tempest/View/src/ViewCache.php
index 78efb5099..00d921cc5 100644
--- a/src/Tempest/View/src/ViewCache.php
+++ b/src/Tempest/View/src/ViewCache.php
@@ -22,7 +22,7 @@ public function __construct(
?ViewCachePool $pool = null,
) {
$this->cachePool = $pool ?? new ViewCachePool(
- directory: path($this->cacheConfig->directory, 'views'),
+ directory: path($this->cacheConfig->directory, 'views')->toString(),
);
}
@@ -38,7 +38,7 @@ public function getCachedViewPath(string $path, Closure $compiledView): string
$this->cachePool->save($cacheItem);
}
- return path($this->cachePool->directory, $cacheItem->getKey() . '.php');
+ return path($this->cachePool->directory, $cacheItem->getKey() . '.php')->toString();
}
protected function getCachePool(): CacheItemPoolInterface
diff --git a/src/Tempest/View/src/ViewCachePool.php b/src/Tempest/View/src/ViewCachePool.php
index ed011ebc2..2522078f6 100644
--- a/src/Tempest/View/src/ViewCachePool.php
+++ b/src/Tempest/View/src/ViewCachePool.php
@@ -55,9 +55,11 @@ public function hasItem(string $key): bool
public function clear(): bool
{
- if (is_dir($this->directory)) {
+ $path = path($this->directory);
+
+ if ($path->isDirectory()) {
/** @phpstan-ignore-next-line */
- arr(glob(path($this->directory, '/*.php')))->each(fn (string $file) => unlink($file));
+ $path->glob('/*.php')->each(fn (string $file) => unlink($file));
rmdir($this->directory);
}
@@ -108,6 +110,6 @@ private function makePath(CacheItemInterface|string $key): string
{
$key = is_string($key) ? $key : $key->getKey();
- return path($this->directory, "/{$key}.php");
+ return path($this->directory, "/{$key}.php")->toString();
}
}
diff --git a/src/Tempest/View/tests/ViewCachePoolTest.php b/src/Tempest/View/tests/ViewCachePoolTest.php
index a88562fc1..989cfa5b3 100644
--- a/src/Tempest/View/tests/ViewCachePoolTest.php
+++ b/src/Tempest/View/tests/ViewCachePoolTest.php
@@ -7,7 +7,6 @@
use Exception;
use PHPUnit\Framework\TestCase;
use function Tempest\path;
-use function Tempest\Support\arr;
use Tempest\View\ViewCachePool;
/**
@@ -30,9 +29,11 @@ protected function setUp(): void
protected function tearDown(): void
{
- if (is_dir(self::DIRECTORY)) {
+ $directory = path(self::DIRECTORY);
+
+ if ($directory->isDirectory()) {
/** @phpstan-ignore-next-line */
- arr(glob(path(self::DIRECTORY, '/*.php')))->each(fn (string $file) => unlink($file));
+ $directory->glob('/*.php')->each(fn (string $file) => unlink($file));
rmdir(self::DIRECTORY);
}
@@ -47,8 +48,8 @@ public function test_get_item(): void
$this->pool->save($item);
- $this->assertFileExists(path(self::DIRECTORY, 'test.php'));
- $this->assertEquals('hi', file_get_contents(path(self::DIRECTORY, 'test.php')));
+ $this->assertFileExists(path(self::DIRECTORY, 'test.php')->toString());
+ $this->assertEquals('hi', file_get_contents(path(self::DIRECTORY, 'test.php')->toString()));
}
public function test_has_item(): void
@@ -88,7 +89,7 @@ public function test_delete_item(): void
$this->pool->save($item);
$this->pool->deleteItem('test');
- $this->assertFileDoesNotExist(path(self::DIRECTORY, 'test.php'));
+ $this->assertFileDoesNotExist(path(self::DIRECTORY, 'test.php')->toString());
}
public function test_delete_items(): void
@@ -101,13 +102,13 @@ public function test_delete_items(): void
$items[1]->set('hi');
$this->pool->save($items[1]);
- $this->assertFileExists(path(self::DIRECTORY, 'a.php'));
- $this->assertFileExists(path(self::DIRECTORY, 'b.php'));
+ $this->assertFileExists(path(self::DIRECTORY, 'a.php')->toString());
+ $this->assertFileExists(path(self::DIRECTORY, 'b.php')->toString());
$this->pool->deleteItems(['a', 'b']);
- $this->assertFileDoesNotExist(path(self::DIRECTORY, 'a.php'));
- $this->assertFileDoesNotExist(path(self::DIRECTORY, 'b.php'));
+ $this->assertFileDoesNotExist(path(self::DIRECTORY, 'a.php')->toString());
+ $this->assertFileDoesNotExist(path(self::DIRECTORY, 'b.php')->toString());
}
public function test_clear_pool(): void
@@ -118,8 +119,8 @@ public function test_clear_pool(): void
$this->pool->save($item);
$this->pool->clear();
- $this->assertFileDoesNotExist(path(self::DIRECTORY, 'test.php'));
- $this->assertDirectoryDoesNotExist(path(self::DIRECTORY));
+ $this->assertFileDoesNotExist(path(self::DIRECTORY, 'test.php')->toString());
+ $this->assertDirectoryDoesNotExist(path(self::DIRECTORY)->toString());
}
public function test_save_deferred(): void
diff --git a/src/Tempest/View/tests/ViewCacheTest.php b/src/Tempest/View/tests/ViewCacheTest.php
index ab51200ed..aa9b405e6 100644
--- a/src/Tempest/View/tests/ViewCacheTest.php
+++ b/src/Tempest/View/tests/ViewCacheTest.php
@@ -7,7 +7,6 @@
use PHPUnit\Framework\TestCase;
use Tempest\Cache\CacheConfig;
use function Tempest\path;
-use function Tempest\Support\arr;
use Tempest\View\ViewCache;
use Tempest\View\ViewCachePool;
@@ -38,9 +37,11 @@ protected function setUp(): void
protected function tearDown(): void
{
- if (is_dir(self::DIRECTORY)) {
+ $directory = path(self::DIRECTORY);
+
+ if ($directory->isDirectory()) {
/** @phpstan-ignore-next-line */
- arr(glob(path(self::DIRECTORY, '/*.php')))->each(fn (string $file) => unlink($file));
+ $directory->glob('/*.php')->each(fn (string $file) => unlink($file));
rmdir(self::DIRECTORY);
}
diff --git a/tests/Integration/Core/PublishesFilesTest.php b/tests/Integration/Core/PublishesFilesTest.php
index 844a9a51e..7c7c73518 100644
--- a/tests/Integration/Core/PublishesFilesTest.php
+++ b/tests/Integration/Core/PublishesFilesTest.php
@@ -47,12 +47,12 @@ public function get_suggested_path(
$appPath = str_replace('\\', '/', $composer->mainNamespace->path); // Normalize windows path
$this->assertSame(
+ expected: path($appPath, $expected)->toString(),
actual: $concreteClass->getSuggestedPath(
className: $className,
pathPrefix: $pathPrefix,
classSuffix: $classSuffix
- ),
- expected: path($appPath, $expected)
+ )
);
}
diff --git a/tests/Integration/Core/RootPathHelperTest.php b/tests/Integration/Core/RootPathHelperTest.php
index ab597691f..b0b5abed8 100644
--- a/tests/Integration/Core/RootPathHelperTest.php
+++ b/tests/Integration/Core/RootPathHelperTest.php
@@ -15,7 +15,7 @@ final class RootPathHelperTest extends FrameworkIntegrationTestCase
{
public function test_can_get_base_path(): void
{
- $this->assertSame(path(realpath($this->root)), root_path());
- $this->assertSame(path(realpath($this->root . '/tests/Fixtures')), root_path('/tests/Fixtures'));
+ $this->assertSame(path(realpath($this->root))->toString(), root_path());
+ $this->assertSame(path(realpath($this->root . '/tests/Fixtures'))->toString(), root_path('/tests/Fixtures'));
}
}
diff --git a/tests/Integration/Http/FileSessionTest.php b/tests/Integration/Http/FileSessionTest.php
index 9d7afac35..40e20ebdf 100644
--- a/tests/Integration/Http/FileSessionTest.php
+++ b/tests/Integration/Http/FileSessionTest.php
@@ -74,7 +74,7 @@ public function test_destroy(): void
{
$session = $this->container->get(Session::class);
- $path = path($this->path, (string) $session->id);
+ $path = path($this->path, (string) $session->id)->toString();
$this->assertFileExists($path);
$session->destroy();
$this->assertFileDoesNotExist($path);
diff --git a/tests/Integration/Http/Static/StaticCleanCommandTest.php b/tests/Integration/Http/Static/StaticCleanCommandTest.php
index 9e6db8e5b..ca483ddb1 100644
--- a/tests/Integration/Http/Static/StaticCleanCommandTest.php
+++ b/tests/Integration/Http/Static/StaticCleanCommandTest.php
@@ -27,7 +27,7 @@ public function test_generate(): void
$root = $this->kernel->root;
- $this->assertFileDoesNotExist(path($root, '/public/static/a/b/index.html'));
- $this->assertFileDoesNotExist(path($root, '/public/static/c/d/index.html'));
+ $this->assertFileDoesNotExist(path($root, '/public/static/a/b/index.html')->toString());
+ $this->assertFileDoesNotExist(path($root, '/public/static/c/d/index.html')->toString());
}
}
diff --git a/tests/Integration/Http/Static/StaticGenerateCommandTest.php b/tests/Integration/Http/Static/StaticGenerateCommandTest.php
index b774f28fd..244cad07e 100644
--- a/tests/Integration/Http/Static/StaticGenerateCommandTest.php
+++ b/tests/Integration/Http/Static/StaticGenerateCommandTest.php
@@ -26,11 +26,11 @@ public function test_static_site_generate_command(): void
$root = $this->kernel->root;
- $this->assertFileExists(path($root, '/public/static/a/b/index.html'));
- $this->assertFileExists(path($root, '/public/static/c/d/index.html'));
+ $this->assertFileExists(path($root, '/public/static/a/b/index.html')->toString());
+ $this->assertFileExists(path($root, '/public/static/c/d/index.html')->toString());
- $b = file_get_contents(path($root, '/public/static/a/b/index.html'));
- $d = file_get_contents(path($root, '/public/static/c/d/index.html'));
+ $b = file_get_contents(path($root, '/public/static/a/b/index.html')->toString());
+ $d = file_get_contents(path($root, '/public/static/c/d/index.html')->toString());
$this->assertStringContainsString('a', $b);
$this->assertStringContainsString('b', $b);
diff --git a/tests/Integration/Support/NamespaceHelperTest.php b/tests/Integration/Support/NamespaceHelperTest.php
new file mode 100644
index 000000000..b6f0d0ef2
--- /dev/null
+++ b/tests/Integration/Support/NamespaceHelperTest.php
@@ -0,0 +1,46 @@
+assertSame('Tempest\\Auth', NamespaceHelper::toMainNamespace('src/Tempest/Auth/src/SomeNewClass.php'));
+ $this->assertSame('Tempest\\Auth\\SomeDirectory', NamespaceHelper::toMainNamespace('src/Tempest/Auth/src/SomeDirectory'));
+ }
+
+ #[Test]
+ public function paths_to_non_registered_namespace_throw(): void
+ {
+ $this->expectException(Exception::class);
+ NamespaceHelper::toRegisteredNamespace('app/SomeNewClass.php');
+ }
+
+ #[Test]
+ public function path_to_namespace(): void
+ {
+ $this->assertSame('App', NamespaceHelper::toNamespace('app/SomeNewClass.php'));
+ $this->assertSame('App\\Foo\\Bar', NamespaceHelper::toNamespace('app/Foo/Bar/SomeNewClass.php'));
+ $this->assertSame('App\\Foo\\Bar\\Baz', NamespaceHelper::toNamespace('app/Foo/Bar/Baz'));
+ $this->assertSame('App\\FooBar', NamespaceHelper::toNamespace('app\\FooBar\\'));
+ $this->assertSame('App\\FooBar', NamespaceHelper::toNamespace('app\\FooBar\\File.php'));
+
+ $this->assertSame('App\\Foo', NamespaceHelper::toNamespace('/home/project-name/app/Foo/Bar.php', root: '/home/project-name'));
+ $this->assertSame('App\\Foo', NamespaceHelper::toNamespace('/home/project-name/app/Foo/Bar.php', root: '/home/project-name/'));
+
+ // we don't support skill issues
+ $this->assertSame('Home\ProjectName\App\Foo', NamespaceHelper::toNamespace('/home/project-name/app/Foo/Bar.php'));
+ }
+}
diff --git a/tests/Integration/Support/PathHelperTest.php b/tests/Integration/Support/PathHelperTest.php
deleted file mode 100644
index f380441ea..000000000
--- a/tests/Integration/Support/PathHelperTest.php
+++ /dev/null
@@ -1,48 +0,0 @@
-assertSame('Tempest\\Auth', PathHelper::toRegisteredNamespace('src/Tempest/Auth/src/SomeNewClass.php'));
- $this->assertSame('Tempest\\Auth\\SomeDirectory', PathHelper::toRegisteredNamespace('src/Tempest/Auth/src/SomeDirectory'));
- $this->assertSame('Tempest\\Auth', PathHelper::toRegisteredNamespace($this->root.'/src/Tempest/Auth/src/SomeNewClass.php'));
- $this->assertSame('Tempest\\Auth\\SomeDirectory', PathHelper::toRegisteredNamespace($this->root.'/src/Tempest/Auth/src/SomeDirectory'));
- }
-
- #[Test]
- public function paths_to_non_registered_namespace_throw(): void
- {
- $this->expectException(Exception::class);
- PathHelper::toRegisteredNamespace('app/SomeNewClass.php');
- }
-
- #[Test]
- public function path_to_namespace(): void
- {
- $this->assertSame('App', PathHelper::toNamespace('app/SomeNewClass.php'));
- $this->assertSame('App\\Foo\\Bar', PathHelper::toNamespace('app/Foo/Bar/SomeNewClass.php'));
- $this->assertSame('App\\Foo\\Bar\\Baz', PathHelper::toNamespace('app/Foo/Bar/Baz'));
- $this->assertSame('App\\FooBar', PathHelper::toNamespace('app\\FooBar\\'));
- $this->assertSame('App\\FooBar', PathHelper::toNamespace('app\\FooBar\\File.php'));
-
- $this->assertSame('App\\Foo', PathHelper::toNamespace('/home/project-name/app/Foo/Bar.php', root: '/home/project-name'));
- $this->assertSame('App\\Foo', PathHelper::toNamespace('/home/project-name/app/Foo/Bar.php', root: '/home/project-name/'));
-
- // we don't support skill issues
- $this->assertSame('Home\ProjectName\App\Foo', PathHelper::toNamespace('/home/project-name/app/Foo/Bar.php'));
- }
-}