Skip to content

Commit

Permalink
Resources changes - StreamResource
Browse files Browse the repository at this point in the history
- renamed method `ResourceFactoryInterface::createResourceFromLocalFile()` to `ResourceFactoryInterface::createResourceFromFile()`
- removed resource class `SimpleResource`
- added resource class `StreamResource`
- added methods `ResourceInterface::getMimeType()` and `ResourceInterface::getFilesize()`
- ResourceFactory can now create a resource from an url
- fixed tests
  • Loading branch information
tg666 committed Mar 12, 2024
1 parent 052408c commit e46f6ce
Show file tree
Hide file tree
Showing 18 changed files with 435 additions and 123 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ use SixtyEightPublishers\FileStorage\FileStorageInterface;

/** @var FileStorageInterface $storage */

# Create a resource from a local file:
$resource = $storage->createResourceFromLocalFile(
# Create a resource from file or url:
$resource = $storage->createResourceFromFile(
$storage->createPathInfo('test/invoice.pdf'),
__DIR__ . '/path/to/invoice.pdf'
);
Expand Down Expand Up @@ -274,7 +274,7 @@ use SixtyEightPublishers\FileStorage\FileStorageInterface;
/** @var FileStorageInterface $storage */

$pathInfo = $storage->createPathInfo('test/avatar.png');
$resource = $storage->createResourceFromLocalFile($pathInfo, __DIR__ . '/path/to/uploaded/file.png');
$resource = $storage->createResourceFromFile($pathInfo, __DIR__ . '/path/to/uploaded/file.png');

$storage->save($resource);

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"require": {
"php": "^8.1",
"ext-json": "*",
"ext-fileinfo": "*",
"league/flysystem": "^3.12",
"psr/log": "^1.1"
},
Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ includes:
- vendor/phpstan/phpstan-nette/rules.neon

parameters:
level: 9
level: 8
paths:
- src
2 changes: 1 addition & 1 deletion src/Asset/AssetsCopier.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function copy(FileStorageInterface $fileStorage, ?LoggerInterface $logger
*/
private function copyAsset(AssetInterface $asset, FileStorageInterface $fileStorage): void
{
$resource = $fileStorage->createResourceFromLocalFile(
$resource = $fileStorage->createResourceFromFile(
$fileStorage->createPathInfo($asset->getOutputPath()),
$asset->getSourceRealPath(),
);
Expand Down
4 changes: 2 additions & 2 deletions src/FileStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public function createResource(PathInfoInterface $pathInfo): ResourceInterface
return $this->resourceFactory->createResource($pathInfo);
}

public function createResourceFromLocalFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
public function createResourceFromFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
{
return $this->resourceFactory->createResourceFromLocalFile($pathInfo, $filename);
return $this->resourceFactory->createResourceFromFile($pathInfo, $filename);
}
}
4 changes: 2 additions & 2 deletions src/Persistence/FilePersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public function save(ResourceInterface $resource, array $config = []): string
$source = $resource->getSource();

if (is_resource($source)) {
$this->filesystemOperator->writeStream($path, $resource->getSource(), $config);
$this->filesystemOperator->writeStream($path, $source, $config);
} else {
$this->filesystemOperator->write($path, $source, $config);
$this->filesystemOperator->write($path, (string) $source, $config);
}

return $path;
Expand Down
176 changes: 160 additions & 16 deletions src/Resource/ResourceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,23 @@

use League\Flysystem\FilesystemException as LeagueFilesystemException;
use League\Flysystem\FilesystemReader;
use League\Flysystem\UnableToRetrieveMetadata;
use SixtyEightPublishers\FileStorage\Exception\FileNotFoundException;
use SixtyEightPublishers\FileStorage\Exception\FilesystemException;
use SixtyEightPublishers\FileStorage\PathInfoInterface;
use function error_clear_last;
use function error_get_last;
use function file_exists;
use function filter_var;
use function fopen;
use function is_file;
use function sprintf;
use function str_starts_with;
use function stream_context_create;
use function stream_get_meta_data;
use function strlen;
use function strtolower;
use function trim;

final class ResourceFactory implements ResourceFactoryInterface
{
Expand All @@ -36,33 +46,167 @@ public function createResource(PathInfoInterface $pathInfo): ResourceInterface
try {
$source = $this->filesystemReader->readStream($path);
} catch (LeagueFilesystemException $e) {
throw new FilesystemException(sprintf(
'Can not read stream from file "%s".',
$path,
), 0, $e);
throw new FilesystemException(
message: sprintf(
'Can not read stream from file "%s".',
$path,
),
previous: $e,
);
}

return new SimpleResource($pathInfo, $source);
return new StreamResource(
pathInfo: $pathInfo,
source: $source,
mimeType: function () use ($path): ?string {
try {
return $this->filesystemReader->mimeType($path);
} catch (LeagueFilesystemException|UnableToRetrieveMetadata $e) {
return null;
}
},
filesize: function () use ($path): ?int {
try {
return $this->filesystemReader->fileSize($path);
} catch (LeagueFilesystemException|UnableToRetrieveMetadata $e) {
return null;
}
},
);
}

public function createResourceFromLocalFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
public function createResourceFromFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
{
return match (true) {
(bool) filter_var($filename, FILTER_VALIDATE_URL) => $this->getResourceFromUrl(
pathInfo: $pathInfo,
url: $filename,
),
file_exists($filename) && is_file($filename) => $this->getResourceFromLocalFile(
pathInfo: $pathInfo,
filename: $filename,
),
default => throw new FileNotFoundException($filename),
};
}

/**
* @throws FilesystemException
*/
private function getResourceFromUrl(PathInfoInterface $pathInfo, string $url): ResourceInterface
{
error_clear_last();

if (!file_exists($filename)) {
throw new FileNotFoundException($filename);
$context = stream_context_create(
options: [
'http' => [
'method' => 'GET',
'protocol_version' => 1.1,
'header' => "Accept-language: en\r\n" . "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36\r\n",
],
],
);

$source = @fopen(
filename: $url,
mode: 'rb',
context: $context,
);

if (false === $source) {
throw new FilesystemException(
message: sprintf(
'Can not read stream from url "%s". %s',
$url,
error_get_last()['message'] ?? '',
),
);
}

$resource = @fopen($filename, 'rb');
$headers = stream_get_meta_data($source)['wrapper_data'] ?? [];

return new StreamResource(
pathInfo: $pathInfo,
source: $source,
mimeType: function () use ($headers): ?string {
return $this->getHeaderValue(
headers: $headers,
name: 'Content-Type',
);
},
filesize: function () use ($headers): ?int {
$filesize = $this->getHeaderValue(
headers: $headers,
name: 'Content-Length',
);

return null !== $filesize ? (int) $filesize : null;
},
);
}

/**
* @throws FilesystemException
*/
private function getResourceFromLocalFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface
{
error_clear_last();

$source = @fopen(
filename: $filename,
mode: 'rb',
);

if (false === $source) {
throw new FilesystemException(
message: sprintf(
'Can not read stream from file "%s". %s',
$filename,
error_get_last()['message'] ?? '',
),
);
}

return new StreamResource(
pathInfo: $pathInfo,
source: $source,
mimeType: function (StreamResource $resource): ?string {
$mimeType = @mime_content_type($resource->getSource());

return false === $mimeType ? null : $mimeType;
},
filesize: function () use ($filename): ?int {
$filesize = @filesize(
filename: $filename,
);

return false === $filesize ? null : $filesize;
},
);
}

/**
* @param array<int, string> $headers
*/
private function getHeaderValue(array $headers, string $name): ?string
{
$name = strtolower($name);

foreach ($headers as $header) {
$header = trim(strtolower($header));

if (!str_starts_with($header, $name . ':')) {
continue;
}

$value = substr(
string: $header,
offset: strlen($name) + 1,
);

if (false === $resource) {
throw new FilesystemException(sprintf(
'Can not read stream from file "%s". %s',
$filename,
error_get_last()['message'] ?? '',
), 0);
return trim($value);
}

return new SimpleResource($pathInfo, $resource);
return null;
}
}
2 changes: 1 addition & 1 deletion src/Resource/ResourceFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ public function createResource(PathInfoInterface $pathInfo): ResourceInterface;
* @throws FileNotFoundException
* @throws FilesystemException
*/
public function createResourceFromLocalFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface;
public function createResourceFromFile(PathInfoInterface $pathInfo, string $filename): ResourceInterface;
}
9 changes: 5 additions & 4 deletions src/Resource/ResourceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ interface ResourceInterface
{
public function getPathInfo(): PathInfoInterface;

/**
* @return string|resource
*/
public function getSource(): mixed;

public function withPathInfo(PathInfoInterface $pathInfo): static;
public function withPathInfo(PathInfoInterface $pathInfo): self;

public function getMimeType(): ?string;

public function getFilesize(): ?int;
}
36 changes: 0 additions & 36 deletions src/Resource/SimpleResource.php

This file was deleted.

0 comments on commit e46f6ce

Please sign in to comment.