Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/fileitem factory #362

Open
wants to merge 13 commits into
base: 2.1
Choose a base branch
from
7 changes: 7 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Symfony\Contracts\Translation\TranslatorInterface;
use Terminal42\NotificationCenterBundle\Backend\AutoSuggester;
use Terminal42\NotificationCenterBundle\BulkyItem\BulkyItemStorage;
use Terminal42\NotificationCenterBundle\BulkyItem\FileItemFactory;
use Terminal42\NotificationCenterBundle\Config\ConfigLoader;
use Terminal42\NotificationCenterBundle\Cron\PruneBulkyItemStorageCron;
use Terminal42\NotificationCenterBundle\DependencyInjection\Terminal42NotificationCenterExtension;
Expand Down Expand Up @@ -59,6 +60,12 @@
])
;

$services->set(FileItemFactory::class)
->args([
service('mime_types')->nullOnInvalid(),
])
;

$services->set(PruneBulkyItemStorageCron::class)
->args([
service(BulkyItemStorage::class),
Expand Down
22 changes: 20 additions & 2 deletions src/BulkyItem/FileItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@
namespace Terminal42\NotificationCenterBundle\BulkyItem;

use Symfony\Component\Filesystem\Filesystem;
use Terminal42\NotificationCenterBundle\Exception\BulkyItem\InvalidFileItemException;

class FileItem implements BulkyItemInterface
{
/**
* @param resource $contents
*
* @throws InvalidFileItemException
*/
private function __construct(
private $contents,
private readonly string $name,
private readonly string $mimeType,
private readonly int $size,
) {
$this->assert('' !== $this->name, 'Name must not be empty');
$this->assert('' !== $this->mimeType, 'Mime type must not be empty');
$this->assert($this->size >= 0, 'File size must not be smaller than 0');
}

public function getName(): string
Expand Down Expand Up @@ -53,24 +59,36 @@ public static function restore($contents, array $meta): BulkyItemInterface
return new self($contents, $meta['name'], $meta['type'], $meta['size']);
}

/**
* @throws InvalidFileItemException
*/
public static function fromPath(string $path, string $name, string $mimeType, int $size): self
{
if (!(new Filesystem())->exists($path)) {
throw new \InvalidArgumentException(\sprintf('The file "%s" does not exist.', $path));
throw new InvalidFileItemException(\sprintf('The file "%s" does not exist.', $path));
}

return new self(fopen($path, 'r'), $name, $mimeType, $size);
}

/**
* @param resource $resource
*
* @throws InvalidFileItemException
*/
public static function fromStream($resource, string $name, string $mimeType, int $size): self
{
if (!\is_resource($resource)) {
throw new \InvalidArgumentException('$contents must be a resource.');
throw new InvalidFileItemException('$contents must be a resource.');
}

return new self($resource, $name, $mimeType, $size);
}

private function assert(bool $condition, string $message): void
{
if (!$condition) {
throw new InvalidFileItemException($message);
}
}
}
44 changes: 44 additions & 0 deletions src/BulkyItem/FileItemFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Terminal42\NotificationCenterBundle\BulkyItem;

use Contao\CoreBundle\Filesystem\FilesystemItem;
use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
use Terminal42\NotificationCenterBundle\Exception\BulkyItem\InvalidFileItemException;

class FileItemFactory
{
public function __construct(private readonly MimeTypeGuesserInterface|null $mimeTypeGuesser = null)
{
}

/**
* @throws InvalidFileItemException if the information cannot be fetched automatically (e.g. missing mime type guesser service)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @throws InvalidFileItemException if the information cannot be fetched automatically (e.g. missing mime type guesser service)
* @throws InvalidFileItemException if the file does not exist)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both comments are correct, you would need to merge them in your suggestion.

*/
public function createFromLocalPath(string $path): FileItem
{
if (!(new Filesystem())->exists($path)) {
throw new InvalidFileItemException(\sprintf('The file "%s" does not exist.', $path));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could use the Symfony Filesystem FileNotFoundException here instead? The we also wouldn't need to add a new exception to NC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's best practice to use domain exceptions.

}

$name = basename($path);
$mimeType = (string) $this->mimeTypeGuesser?->guessMimeType($path);
$size = (int) filesize($path);

return FileItem::fromPath($path, $name, $mimeType, $size);
}

/**
* @throws InvalidFileItemException
*/
public function createFromVfsFilesystemItem(FilesystemItem $file, VirtualFilesystemInterface $virtualFilesystem): FileItem
{
$stream = $virtualFilesystem->readStream($file->getPath());

return FileItem::fromStream($stream, $file->getName(), $file->getMimeType(), $file->getFileSize());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does $file->getName() return the "base name" only, or should we apply basename() as well here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does whatever the implementation of the virtualfilesystem does. We don't care here.

}
}
9 changes: 9 additions & 0 deletions src/Exception/BulkyItem/InvalidFileItemException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Terminal42\NotificationCenterBundle\Exception\BulkyItem;

class InvalidFileItemException extends \InvalidArgumentException
{
}
41 changes: 41 additions & 0 deletions tests/BulkyItem/FileItemFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Terminal42\NotificationCenterBundle\Test\BulkyItem;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Mime\MimeTypes;
use Terminal42\NotificationCenterBundle\BulkyItem\FileItemFactory;
use Terminal42\NotificationCenterBundle\Test\VirtualFilesystemTestTrait;

class FileItemFactoryTest extends TestCase
{
use VirtualFilesystemTestTrait;

public function testCreateFromLocalPath(): void
{
$factory = new FileItemFactory(new MimeTypes());
$item = $factory->createFromLocalPath(__DIR__.'/../Fixtures/name.jpg');
$this->assertSame('name.jpg', $item->getName());
$this->assertSame('image/jpeg', $item->getMimeType());
$this->assertSame(333, $item->getSize());
$this->assertIsResource($item->getContents());
}

public function testCreateFromVfsFilesystemItem(): void
{
$vfsCollection = $this->createVfsCollection();
$vfs = $vfsCollection->get('files');
$vfs->write('media/name.jpg', file_get_contents(__DIR__.'/../Fixtures/name.jpg'));

$item = $vfs->get('media/name.jpg');

$factory = new FileItemFactory(new MimeTypes());
$item = $factory->createFromVfsFilesystemItem($item, $vfs);
$this->assertSame('name.jpg', $item->getName());
$this->assertSame('image/jpeg', $item->getMimeType());
$this->assertSame(333, $item->getSize());
$this->assertIsResource($item->getContents());
}
}
36 changes: 36 additions & 0 deletions tests/BulkyItem/FileItemTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Terminal42\NotificationCenterBundle\Test\BulkyItem;

use PHPUnit\Framework\TestCase;
use Terminal42\NotificationCenterBundle\BulkyItem\FileItem;
use Terminal42\NotificationCenterBundle\Exception\BulkyItem\InvalidFileItemException;

class FileItemTest extends TestCase
{
public function testCannotCreateEmptyNameFileItem(): void
{
$this->expectException(InvalidFileItemException::class);
$this->expectExceptionMessage('Name must not be empty');

FileItem::fromPath(__DIR__.'/../Fixtures/name.jpg', '', 'image/jpg', 0);
}

public function testCannotCreateEmptyMimeTypeFileItem(): void
{
$this->expectException(InvalidFileItemException::class);
$this->expectExceptionMessage('Mime type must not be empty');

FileItem::fromPath(__DIR__.'/../Fixtures/name.jpg', 'name.jpg', '', 0);
}

public function testCannotCreateInvalidFileSizeFileItem(): void
{
$this->expectException(InvalidFileItemException::class);
$this->expectExceptionMessage('File size must not be smaller than 0');

FileItem::fromPath(__DIR__.'/../Fixtures/name.jpg', 'name.jpg', 'image/jpg', -42);
}
}
34 changes: 34 additions & 0 deletions tests/VirtualFilesystemTestTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Terminal42\NotificationCenterBundle\Test;

use Contao\CoreBundle\Filesystem\Dbafs\DbafsManager;
use Contao\CoreBundle\Filesystem\MountManager;
use Contao\CoreBundle\Filesystem\VirtualFilesystem;
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
use Terminal42\NotificationCenterBundle\Test\BulkyItem\InMemoryDbafs;
use Terminal42\NotificationCenterBundle\Test\BulkyItem\VirtualFilesystemCollection;

trait VirtualFilesystemTestTrait
{
private function createVfsCollection(): VirtualFilesystemCollection
{
$mountManager = (new MountManager())
->mount(new InMemoryFilesystemAdapter(), 'files')
->mount(new InMemoryFilesystemAdapter(), 'bulky_item')
;

$dbafsManager = new DbafsManager();
$dbafsManager->register(new InMemoryDbafs(), 'files');
$dbafsManager->register(new InMemoryDbafs(), 'bulky_item');

$vfsCollection = new VirtualFilesystemCollection();
$vfsCollection->add(new VirtualFilesystem($mountManager, $dbafsManager, 'files'));
$vfsCollection->add(new VirtualFilesystem($mountManager, $dbafsManager, 'bulky_item'));
$vfsCollection->add(new VirtualFilesystem($mountManager, $dbafsManager, '')); // Global one

return $vfsCollection;
}
}
Loading