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
13 changes: 12 additions & 1 deletion src/DependencyInjection/ChamberOrchestraFileExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private function registerFileSystemStorage(ContainerBuilder $container, string $
}

/**
* @param array{driver: string, path: string, uri_prefix: string|null, enabled?: bool, bucket?: string|null, region?: string|null, endpoint?: string|null} $storage
* @param array{driver: string, path: string, uri_prefix: string|null, enabled?: bool, bucket?: string|null, region?: string|null, endpoint?: string|null, path_style?: bool, credentials?: array{key: string, secret: string}} $storage
*/
private function registerS3Storage(ContainerBuilder $container, string $serviceId, string $name, array $storage): void
{
Expand All @@ -123,6 +123,17 @@ private function registerS3Storage(ContainerBuilder $container, string $serviceI
$clientArgs['endpoint'] = $storage['endpoint'];
}

if ($storage['path_style'] ?? false) {
$clientArgs['use_path_style_endpoint'] = true;
}

if (isset($storage['credentials'])) {
$clientArgs['credentials'] = [
'key' => $storage['credentials']['key'],
'secret' => $storage['credentials']['secret'],
];
}

$clientDefinition = new Definition(S3Client::class, [$clientArgs]);
$container->setDefinition($serviceId.'.client', $clientDefinition);

Expand Down
10 changes: 10 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('endpoint')
->defaultNull()
->end()
->booleanNode('path_style')
->defaultFalse()
->info('Use path-style addressing (required for LocalStack and some S3-compatible services).')
->end()
->arrayNode('credentials')
->children()
->scalarNode('key')->isRequired()->end()
->scalarNode('secret')->isRequired()->end()
->end()
->end()
->end()
->validate()
->ifTrue(static fn (array $v): bool => 's3' === ($v['driver'] ?? 'file_system') && (null === $v['bucket'] || '' === $v['bucket']))
Expand Down
4 changes: 2 additions & 2 deletions src/Handler/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function upload(ExtensionMetadataInterface $metadata, object $object, str
$resolvedPath = $storage->resolvePath($relativePath);
$uri = $storage->resolveUri($relativePath);

$file = new File($resolvedPath, $uri);
$file = new File($resolvedPath, $uri, $relativePath);
$metadata->setFieldValue($object, $inversedBy, $file);

$this->dispatcher->dispatch(new PostUploadEvent($object, $file, $fieldName));
Expand Down Expand Up @@ -163,7 +163,7 @@ public function inject(ExtensionMetadataInterface $metadata, object $object, str
$path = $storage->resolvePath($relativePath);
$uri = $storage->resolveUri($relativePath);

$file = new File($path, $uri);
$file = new File($path, $uri, $relativePath);
$metadata->setFieldValue($object, $fieldName, $file);
}

Expand Down
49 changes: 47 additions & 2 deletions src/Model/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,58 @@ class File extends \Symfony\Component\HttpFoundation\File\File implements FileIn
{
use ImageTrait;

public function __construct(string $path, public readonly ?string $uri = null)
{
public function __construct(
string $path,
public readonly ?string $uri = null,
private readonly ?string $relativePath = null,
) {
parent::__construct($path, false);
}

public function getUri(): ?string
{
return $this->uri;
}

public function getRelativePath(): ?string
{
return $this->relativePath;
}

#[\Override]
public function isFile(): bool
{
if (parent::isFile()) {
return true;
}

return null !== $this->uri;
}

#[\Override]
public function getMimeType(): ?string
{
if (parent::isFile()) {
return parent::getMimeType();
}

if (null !== $this->uri) {
$ext = \pathinfo($this->getPathname(), \PATHINFO_EXTENSION);

return match (\strtolower($ext)) {
'jpg', 'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
'avif' => 'image/avif',
'svg' => 'image/svg+xml',
'pdf' => 'application/pdf',
'mp4' => 'video/mp4',
'mov' => 'video/quicktime',
default => null,
};
}

return null;
}
}
2 changes: 2 additions & 0 deletions src/Model/FileInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
interface FileInterface
{
public function getUri(): ?string;

public function getRelativePath(): ?string;
}
11 changes: 9 additions & 2 deletions src/Storage/S3Storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ public function __construct(
?string $uriPrefix = null,
) {
$this->bucket = $bucket;
$this->uriPrefix = null !== $uriPrefix ? '/'.\trim($uriPrefix, '/') : null;

if (null === $uriPrefix) {
$this->uriPrefix = null;
} elseif (\str_contains($uriPrefix, '://')) {
$this->uriPrefix = \rtrim($uriPrefix, '/');
} else {
$this->uriPrefix = '/'.\trim($uriPrefix, '/');
}
}

public function upload(File $file, NamingStrategyInterface $namingStrategy, string $prefix = ''): string
Expand Down Expand Up @@ -90,7 +97,7 @@ public function resolveUri(string $path): ?string
return $this->client->getObjectUrl($this->bucket, \ltrim($path, '/'));
}

return $this->uriPrefix.$path;
return $this->uriPrefix.'/'.\ltrim($path, '/');
}

public function resolveRelativePath(string $path, string $prefix = ''): string
Expand Down
9 changes: 8 additions & 1 deletion tests/Unit/Model/ImageTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,17 @@ public function testIsImageReturnsFalseForNonImage(): void
self::assertFalse($file->isImage());
}

public function testIsImageReturnsFalseForNonExistentFile(): void
public function testIsImageReturnsTrueForNonExistentFileWithUri(): void
{
$file = new File('/nonexistent/path.png', '/uploads/path.png');

self::assertTrue($file->isImage());
}

public function testIsImageReturnsFalseForNonExistentFileWithoutUri(): void
{
$file = new File('/nonexistent/path.png');

self::assertFalse($file->isImage());
}

Expand Down
Loading