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
24 changes: 0 additions & 24 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2406,30 +2406,6 @@ parameters:
count: 1
path: src/Phaseolies/Support/Storage/FileSystem.php

-
message: '#^PHPDoc tag @method for method Phaseolies\\Support\\Storage\\StorageFileService\:\:store\(\) parameter \#2 \$file contains unknown class Phaseolies\\Support\\Storage\\File\.$#'
identifier: class.notFound
count: 1
path: src/Phaseolies/Support/Storage/StorageFileService.php

-
message: '#^PHPDoc tag @method has invalid value \(\(\)\: string\)\: Unexpected token "\\n \* ", expected type at offset 130 on line 3$#'
identifier: phpDoc.parseError
count: 1
path: src/Phaseolies/Support/Storage/StorageFileService.php

-
message: '#^PHPDoc tag @method has invalid value \(\(string\|array \$path\)\: bool\)\: Unexpected token "\\n \* ", expected type at offset 259 on line 6$#'
identifier: phpDoc.parseError
count: 1
path: src/Phaseolies/Support/Storage/StorageFileService.php

-
message: '#^PHPDoc tag @method has invalid value \(static get\(string \$path\)\: \?string\)\: Unexpected token "\\n \* ", expected type at offset 175 on line 4$#'
identifier: phpDoc.parseError
count: 1
path: src/Phaseolies/Support/Storage/StorageFileService.php

-
message: '#^Instanceof between Iterator and Traversable will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
Expand Down
9 changes: 6 additions & 3 deletions src/Phaseolies/Launchers/FacadeLauncher.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Phaseolies\Support\ViteManager;
use Phaseolies\Support\StringService;
use Phaseolies\Support\Storage\StorageFileService;
use Phaseolies\Support\Storage\Contracts\StorageInterface;
use Phaseolies\Signals\SignalBus;
use Phaseolies\Support\Session;
use Phaseolies\Session\Contracts\SessionInterface;
Expand Down Expand Up @@ -88,9 +89,11 @@ public function register()
return new UrlGenerator(env('APP_URL') ?? config('app.url'));
});

// Bind the 'storage' service to a singleton instance of the Storage class.
// This handles file uploads.
$this->app->singleton('storage', StorageFileService::class);
// Bind the StorageInterface contract to a singleton StorageFileService and
// keep the legacy 'storage' key as an alias of it, so both resolve to the
// same instance. This handles file uploads.
$this->app->singleton(StorageInterface::class, StorageFileService::class);
$this->app->alias(StorageInterface::class, 'storage');

// Bind the 'vite' service to a singleton instance of the Vite manager.
// This resolves dev-server and built frontend assets.
Expand Down
4 changes: 2 additions & 2 deletions src/Phaseolies/Support/Facades/Storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
use Phaseolies\Facade\BaseFacade;

/**
* @method static mixed disk(?string $name = null)
* @method static \Phaseolies\Support\Storage\IFileSystem disk(string $name)
* @method static ?string getDiskPath(string $disk)
*
* @see \Phaseolies\Support\Storage\StorageFileService
* @see \Phaseolies\Support\Storage\Contracts\StorageInterface
*/
class Storage extends BaseFacade
{
Expand Down
24 changes: 24 additions & 0 deletions src/Phaseolies/Support/Storage/Contracts/StorageInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Phaseolies\Support\Storage\Contracts;

use Phaseolies\Support\Storage\IFileSystem;

interface StorageInterface
{
/**
* Get a filesystem disk by name.
*
* @param string $name
* @return IFileSystem
*/
public function disk(string $name): IFileSystem;

/**
* Get the configured root path for a disk.
*
* @param string $disk
* @return string|null
*/
public function getDiskPath(string $disk): ?string;
}
24 changes: 21 additions & 3 deletions src/Phaseolies/Support/Storage/FileSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,18 @@ protected function resolvePath(string $relativePath): string
* @param File $file
* @param string|null $fileName
* @return bool
* @throws FileNotFoundException When the uploaded temporary file is missing
*/
public function put(string $path, File $file, ?string $fileName = null): bool
{
$fileName = $fileName ?? $this->getFileName($file);
$stream = fopen($file->getClientOriginalPath(), 'rb');
$source = $file->getClientOriginalPath();

if (!is_file($source)) {
throw new FileNotFoundException("Uploaded file does not exist at path {$source}. It may have been moved already.");
}

$stream = fopen($source, 'rb');

if (is_resource($stream)) {
$this->writeStream($path, $fileName, $stream);
Expand Down Expand Up @@ -189,15 +196,26 @@ public function destinationFile(string $path, $fileName): string
}

/**
* return project base path in os
* Get the filesystem base path.
*
* @return string
*/
public function storeageBasePath(): string
public function storageBasePath(): string
{
return $this->filePath;
}

/**
* Get the filesystem base path.
*
* @deprecated Misspelled name kept for backward compatibility, use storageBasePath() instead.
* @return string
*/
public function storeageBasePath(): string
{
return $this->storageBasePath();
}

/**
* Determine if the given path is a file.
*
Expand Down
46 changes: 45 additions & 1 deletion src/Phaseolies/Support/Storage/IFileSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,48 @@

namespace Phaseolies\Support\Storage;

interface IFileSystem {}
use Phaseolies\Support\File;

interface IFileSystem
{
/**
* Store a file at the given path.
*
* @param string $path
* @param File $file
* @param string|null $fileName
* @return bool
*/
public function store(string $path, File $file, ?string $fileName = null): bool;

/**
* Get the filesystem base path.
*
* @return string
*/
public function storageBasePath(): string;

/**
* Get the absolute path for a stored file.
*
* @param string $path
* @return string|null
*/
public function get(string $path): ?string;

/**
* Read the contents of a stored file.
*
* @param string $path
* @return string
*/
public function content(string $path): string;

/**
* Delete one or more stored files.
*
* @param string|array $path
* @return bool
*/
public function delete(string|array $path): bool;
}
12 changes: 1 addition & 11 deletions src/Phaseolies/Support/Storage/LocalFileSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,6 @@ public function store(string $path, File $file, ?string $fileName = null): bool
return $this->put($this->isDirectoryExists($path), $file, $fileName);
}

/**
* return project base path in os
*
* @return string
*/
public function storeageBasePath(): string
{
return $this->filePath;
}

/**
* Get the contents of a file.
*
Expand All @@ -53,7 +43,7 @@ public function get(string $path): ?string
* @return string
* @throws \Phaseolies\Support\Storage\FileNotFoundException
*/
public function content($path)
public function content(string $path): string
{
$fullPath = $this->resolvePath($path);

Expand Down
12 changes: 1 addition & 11 deletions src/Phaseolies/Support/Storage/PublicFileSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ public function store(string $path, File $file, ?string $fileName = null): bool
return $this->put($this->isDirectoryExists($path), $file, $fileName);
}

/**
* return project base path in os
*
* @return string
*/
public function storeageBasePath(): string
{
return $this->filePath;
}

/**
* Get the contents of a file.
*
Expand All @@ -56,7 +46,7 @@ public function get(string $path): ?string
*
* @throws \Phaseolies\Support\Storage\FileNotFoundException
*/
public function content($path)
public function content(string $path): string
{
$fullPath = $this->resolvePath($path);

Expand Down
23 changes: 9 additions & 14 deletions src/Phaseolies/Support/Storage/StorageFileService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,9 @@

namespace Phaseolies\Support\Storage;

use Phaseolies\Support\Storage\LocalFileSystem;
use Phaseolies\Support\Storage\DiskNotFoundException;
use Phaseolies\Support\Storage\Contracts\StorageInterface;

/**
* @method static store(string $path, File $file, ?string $fileName = null): bool
* @method static storeageBasePath(): string
* @method static get(string $path): ?string
* @method static content($path)
* @method static delete(string|array $path): bool
* @see \Phaseolies\Support\Storage\StorageFileService
*/
class StorageFileService
class StorageFileService implements StorageInterface
{
/**
* The array of resolved filesystem drivers.
Expand All @@ -25,13 +16,17 @@ class StorageFileService
/**
* Get a filesystem instance.
*
* @param string|null $name
* @return \Phaseolies\Support\Storage\IFileSystem
* @param string $name
* @return IFileSystem
*/
public function disk($name = null)
public function disk(string $name): IFileSystem
{
$path = $this->getDiskPath($name);

if ($path === null && in_array($name, ['local', 'public'], true)) {
throw new DiskNotFoundException("Disk [{$name}] has no root. Set filesystem.disks.{$name}.root.");
}

return match ($name) {
'local' => app(LocalFileSystem::class, [$path]),
'public' => app(PublicFileSystem::class, [$path]),
Expand Down
54 changes: 41 additions & 13 deletions tests/Storage/FileSystemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,26 @@ public function testDestinationFileBuildsCorrectPath()
$this->assertEquals($this->tmpDir . '/uploads/image.jpg', $result);
}

public function testStoreageBasePathReturnsFilePath()
public function testStorageBasePathReturnsFilePath()
{
$this->assertEquals($this->tmpDir, $this->fs->storeageBasePath());
$this->assertSame($this->tmpDir, $this->fs->storageBasePath());
}

public function testDeprecatedStoreageBasePathAliasReturnsSameValue()
{
$this->assertSame($this->fs->storageBasePath(), $this->fs->storeageBasePath());
}

public function testDeprecatedAliasDelegatesToStorageBasePath()
{
$fs = new class('/original') extends FileSystem {
public function storageBasePath(): string
{
return '/overridden';
}
};

$this->assertSame('/overridden', $fs->storeageBasePath());
}

public function testIsDirectoryExistsCreatesDirectoryIfMissing()
Expand Down Expand Up @@ -171,7 +188,7 @@ public function testGetFileNameUsesConfiguredBaseNameAndDetectedExtension()
$this->assertSame('profile_image.png', $customFs->getFileName($file));
}

public function testPutReturnsFalseWhenSourceStreamCannotBeOpened()
public function testPutThrowsAClearErrorWhenTheTemporaryFileIsGone()
{
$file = new \Phaseolies\Support\File([
'name' => 'missing.txt',
Expand All @@ -181,20 +198,31 @@ public function testPutReturnsFalseWhenSourceStreamCannotBeOpened()
'size' => 0,
]);

$warning = null;
set_error_handler(static function (int $severity, string $message) use (&$warning): bool {
$warning = $message;
return true;
}, E_WARNING);
$this->expectException(\Phaseolies\Support\Storage\FileNotFoundException::class);
$this->expectExceptionMessage('may have been moved already');

$this->fs->put('uploads', $file);
}

public function testPutNamesTheMissingTemporaryPathInTheError()
{
$source = $this->tmpDir . '/gone-' . uniqid() . '.txt';
$file = new \Phaseolies\Support\File([
'name' => 'photo.png',
'type' => 'image/png',
'tmp_name' => $source,
'error' => UPLOAD_ERR_OK,
'size' => 0,
]);

try {
$this->assertFalse($this->fs->put('uploads', $file));
} finally {
restore_error_handler();
$this->fs->put('uploads', $file);
$this->fail('Expected a FileNotFoundException.');
} catch (\Phaseolies\Support\Storage\FileNotFoundException $e) {
$this->assertSame("Uploaded file does not exist at path {$source}. It may have been moved already.", $e->getMessage());
}

$this->assertNotNull($warning);
$this->assertStringContainsString('Failed to open stream', $warning);
$this->assertFileDoesNotExist($this->tmpDir . '/uploads/photo.png');
}

public function testPutWritesUsingConfiguredCustomFileName()
Expand Down
Loading
Loading