Skip to content

Instantly share code, notes, and snippets.

@simshaun
Last active August 4, 2016 21:07
Show Gist options
  • Save simshaun/3ef497b7c092b1093634470cc37113f6 to your computer and use it in GitHub Desktop.
Save simshaun/3ef497b7c092b1093634470cc37113f6 to your computer and use it in GitHub Desktop.
Makes use of Flysystem for fs abstraction and is setup in a manner that lets me easily swap out local files (single-server apps) with remote files (e.g. S3, for multi-server apps) by tweaking the service definitions. The Avatar file is just an example of how I might handle an image. Validation is enforced before this handler is called.
<?php
namespace AppBundle\File;
use AppBundle\File\Namer\NamerInterface;
use AppBundle\File\UrlGenerator\FileUrlGeneratorInterface;
use League\Flysystem\Filesystem;
abstract class AbstractFile
{
/** @var Filesystem */
protected $filesystem;
/** @var NamerInterface */
protected $namer;
/** @var FileUrlGeneratorInterface */
protected $urlGenerator;
public function setFilesystem(Filesystem $filesystem)
{
$this->filesystem = $filesystem;
}
public function setNamer(NamerInterface $namer)
{
$this->namer = $namer;
}
public function setUrlGenerator(FileUrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function getPublicUrl($filename)
{
return $this->urlGenerator->generate($this->getBasePath().$filename);
}
public function getPublicUrlBase()
{
return $this->urlGenerator->generate($this->getBasePath());
}
protected function getBasePath()
{
return;
}
}
<?php
namespace AppBundle\File\Namer;
use Symfony\Component\HttpFoundation\File\UploadedFile;
interface NamerInterface
{
public function getName(UploadedFile $file);
}
<?php
namespace AppBundle\File\Namer;
use RandomLib;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* This namer keeps the original filename but appends a random string to the end.
*/
class OriginalRandomNamer implements NamerInterface
{
private $opts = [
'length' => 6,
];
/**
* @param int $length of random portion of filename
*/
public function setLength($length)
{
$this->opts['length'] = (int) $length;
}
public function getName(UploadedFile $file)
{
$factory = new RandomLib\Factory();
$generator = $factory->getMediumStrengthGenerator();
return sprintf(
'%s_%s.%s',
pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME),
$generator->generateString($this->opts['length'], RandomLib\Generator::CHAR_ALNUM),
$file->getClientOriginalExtension()
);
}
}
<?php
namespace AppBundle\File\Namer;
use RandomLib;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* This namer generates a random filename.
*/
class RandomNamer implements NamerInterface
{
private $opts = [
'length' => 6,
];
/**
* @param int $length of random portion of filename
*/
public function setLength($length)
{
$this->opts['length'] = (int) $length;
}
public function getName(UploadedFile $file)
{
$factory = new RandomLib\Factory();
$generator = $factory->getMediumStrengthGenerator();
return sprintf(
'%s.%s',
$generator->generateString($this->opts['length'], RandomLib\Generator::CHAR_ALNUM),
$file->getClientOriginalExtension()
);
}
}
<?php
namespace AppBundle\File\UrlGenerator;
interface FileUrlGeneratorInterface
{
public function generate($path);
}
<?php
namespace AppBundle\File\UrlGenerator;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class Local implements FileUrlGeneratorInterface
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public function generate($path)
{
return $this->router->generate('app_file', ['path' => $path]);
}
}
<?php
namespace AppBundle\File\User;
use AppBundle\Entity\User;
use AppBundle\File\AbstractFile;
use Intervention\Image\Constraint;
use Intervention\Image\ImageManager;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class AvatarFile extends AbstractFile
{
private $imageManager;
public function __construct(ImageManager $imageManager)
{
$this->imageManager = $imageManager;
}
protected function getBasePath()
{
return 'user/avatar/';
}
public function upload(User $user)
{
$file = $user->getAvatarFile();
if (!$file instanceof UploadedFile) {
return;
}
if (!$file->isValid()) {
throw new FileException($file->getErrorMessage());
}
$name = $user->getId().'-'.$this->namer->getName($file);
$image = $this->imageManager->make($file->openFile());
$extension = strtolower($file->getExtension());
$resizeConstraint = function (Constraint $constraint) {
$constraint->aspectRatio();
$constraint->upsize();
};
$image->backup();
$image->fit(400, 400, $resizeConstraint);
$this->filesystem->write($this->getBasePath().'400/'.$name, $image->encode($extension, 85));
$image->reset();
$image->fit(50, 50, $resizeConstraint);
$this->filesystem->write($this->getBasePath().'50/'.$name, $image->encode($extension, 85));
$image->reset();
return $name;
}
}
#
# Example file service
#
file.user.avatar:
class: AppBundle\File\User\AvatarFile
parent: file.abstract
arguments: ['@intervention.image_manager']
calls:
- [setNamer, ['@file.namer.random']]
#
# Files
#
file.abstract:
class: AppBundle\File\AbstractFile
abstract: true;
calls:
- [setFilesystem, ['@file.system.default']]
- [setNamer, ['@file.namer.default']]
- [setUrlGenerator, ['@file.url_generator.default']]
file.system.default:
alias: file.system.local
file.url_generator.default:
alias: file.url_generator.local
file.namer.default:
alias: file.namer.original_random
file.namer.random:
class: AppBundle\File\Namer\RandomNamer
file.namer.original_random:
class: AppBundle\File\Namer\OriginalRandomNamer
# Local
file.system.local:
class: League\Flysystem\Filesystem
arguments: ['@file.system_adapter.local']
file.url_generator.local:
class: AppBundle\File\UrlGenerator\Local
arguments: ['@router']
file.system_adapter.local:
class: League\Flysystem\Adapter\Local
arguments: ['%upload_path%']
app_file:
path: "%upload_folder%/{path}"
requirements:
path: .*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment