diff --git a/Adapter/AdapterInterface.php b/Adapter/AdapterInterface.php index 3d5fcc18..6b459b15 100644 --- a/Adapter/AdapterInterface.php +++ b/Adapter/AdapterInterface.php @@ -2,8 +2,6 @@ namespace Vich\UploaderBundle\Adapter; -use Doctrine\Common\EventArgs; - /** * AdapterInterface. * @@ -12,26 +10,26 @@ interface AdapterInterface { /** - * Gets the mapped object from the event arguments. + * Gets the mapped object from an event. * - * @param EventArgs $e The event arguments. - * @return object The mapped object. + * @param object $event The event argument. + * @return object The mapped object. */ - public function getObjectFromArgs(EventArgs $e); + public function getObjectFromEvent($event); /** - * Recomputes the change set for the object. + * Recomputes the change set for the given object. * - * @param EventArgs $e The event arguments. + * @param object $event The event arguments. */ - public function recomputeChangeSet(EventArgs $e); + public function recomputeChangeSet($event); /** - * Gets the reflection class for the object taking - * proxies into account. + * Gets class name for the object, taking proxies into account. + * + * @param object $object The object. * - * @param object $obj The object. - * @return \ReflectionClass The reflection class. + * @return string The FQCN of the className. */ - public function getReflectionClass($obj); + public function getClassName($object); } diff --git a/Adapter/ODM/MongoDB/MongoDBAdapter.php b/Adapter/ODM/MongoDB/MongoDBAdapter.php index 96a95f84..3c4c34b3 100644 --- a/Adapter/ODM/MongoDB/MongoDBAdapter.php +++ b/Adapter/ODM/MongoDB/MongoDBAdapter.php @@ -3,7 +3,6 @@ namespace Vich\UploaderBundle\Adapter\ODM\MongoDB; use Vich\UploaderBundle\Adapter\AdapterInterface; -use Doctrine\Common\EventArgs; use Doctrine\ODM\MongoDB\Proxy\Proxy; /** @@ -16,33 +15,36 @@ class MongoDBAdapter implements AdapterInterface /** * {@inheritDoc} */ - public function getObjectFromArgs(EventArgs $e) + public function getObjectFromEvent($event) { - return $e->getDocument(); + /* @var $event \Doctrine\Common\EventArgs */ + + return $event->getDocument(); } /** * {@inheritDoc} */ - public function recomputeChangeSet(EventArgs $e) + public function recomputeChangeSet($event) { - $obj = $this->getObjectFromArgs($e); + /* @var $event \Doctrine\Common\EventArgs */ + $object = $this->getObjectFromEvent($event); - $dm = $e->getDocumentManager(); + $dm = $event->getDocumentManager(); $uow = $dm->getUnitOfWork(); - $metadata = $dm->getClassMetadata(get_class($obj)); - $uow->recomputeSingleDocumentChangeSet($metadata, $obj); + $metadata = $dm->getClassMetadata(get_class($object)); + $uow->recomputeSingleDocumentChangeSet($metadata, $object); } /** * {@inheritDoc} */ - public function getReflectionClass($obj) + public function getClassName($object) { - if ($obj instanceof Proxy) { - return new \ReflectionClass(get_parent_class($obj)); + if ($object instanceof Proxy) { + return get_parent_class($object); + } else { + return get_class($object); } - - return new \ReflectionClass($obj); } } diff --git a/Adapter/ORM/DoctrineORMAdapter.php b/Adapter/ORM/DoctrineORMAdapter.php index 325c2a35..38d70c00 100644 --- a/Adapter/ORM/DoctrineORMAdapter.php +++ b/Adapter/ORM/DoctrineORMAdapter.php @@ -3,7 +3,6 @@ namespace Vich\UploaderBundle\Adapter\ORM; use Vich\UploaderBundle\Adapter\AdapterInterface; -use Doctrine\Common\EventArgs; use Doctrine\Common\Persistence\Proxy; /** @@ -16,33 +15,36 @@ class DoctrineORMAdapter implements AdapterInterface /** * {@inheritDoc} */ - public function getObjectFromArgs(EventArgs $e) + public function getObjectFromEvent($event) { - return $e->getEntity(); + /* @var $event \Doctrine\Common\EventArgs */ + + return $event->getEntity(); } /** * {@inheritDoc} */ - public function recomputeChangeSet(EventArgs $e) + public function recomputeChangeSet($event) { - $obj = $this->getObjectFromArgs($e); + /* @var $event \Doctrine\Common\EventArgs */ + $object = $this->getObjectFromEvent($event); - $em = $e->getEntityManager(); + $em = $event->getEntityManager(); $uow = $em->getUnitOfWork(); - $metadata = $em->getClassMetadata(get_class($obj)); - $uow->recomputeSingleEntityChangeSet($metadata, $obj); + $metadata = $em->getClassMetadata(get_class($object)); + $uow->recomputeSingleEntityChangeSet($metadata, $object); } /** * {@inheritDoc} */ - public function getReflectionClass($obj) + public function getClassName($object) { - if ($obj instanceof Proxy) { - return new \ReflectionClass(get_parent_class($obj)); + if ($object instanceof Proxy) { + return get_parent_class($object); + } else { + return get_class($object); } - - return new \ReflectionClass($obj); } } diff --git a/Adapter/Propel/PropelAdapter.php b/Adapter/Propel/PropelAdapter.php new file mode 100644 index 00000000..1faf80c3 --- /dev/null +++ b/Adapter/Propel/PropelAdapter.php @@ -0,0 +1,38 @@ + + */ +class PropelAdapter implements AdapterInterface +{ + /** + * {@inheritDoc} + */ + public function getObjectFromEvent($event) + { + /* @var $event \Symfony\Component\EventDispatcher\GenericEvent */ + + return $event->getSubject(); + } + + /** + * {@inheritDoc} + */ + public function recomputeChangeSet($event) + { + } + + /** + * {@inheritDoc} + */ + public function getClassName($object) + { + return get_class($object); + } +} diff --git a/DependencyInjection/CompilerPass/RegisterPropelModelsPass.php b/DependencyInjection/CompilerPass/RegisterPropelModelsPass.php new file mode 100644 index 00000000..57b4eca6 --- /dev/null +++ b/DependencyInjection/CompilerPass/RegisterPropelModelsPass.php @@ -0,0 +1,32 @@ + + */ +class RegisterPropelModelsPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if ($container->getParameter('vich_uploader.driver') !== 'propel') { + return; + } + + $propelListener = $container->getDefinition('vich_uploader.listener.uploader.propel'); + $metadata = $container->get('vich_uploader.metadata_reader'); + foreach ($metadata->getUploadableClasses() as $class) { + $propelListener->addTag('propel.event_subscriber', array( + 'class' => $class + )); + } + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index aa02d757..ca8755e8 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -2,6 +2,7 @@ namespace Vich\UploaderBundle\DependencyInjection; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -22,12 +23,37 @@ public function getConfigTreeBuilder() $tb = new TreeBuilder(); $root = $tb->root('vich_uploader'); + $supportedDbDrivers = array('orm', 'odm', 'propel'); + $root ->children() - ->scalarNode('db_driver')->isRequired()->end() + ->scalarNode('db_driver') + ->isRequired() + ->beforeNormalization() + ->ifString() + ->then(function ($v) { return strtolower($v); }) + ->end() + ->validate() + ->ifNotInArray($supportedDbDrivers) + ->thenInvalid('The db driver %s is not supported. Please choose one of ' . implode(', ', $supportedDbDrivers)) + ->end() + ->end() ->scalarNode('storage')->defaultValue('vich_uploader.storage.file_system')->end() ->scalarNode('twig')->defaultTrue()->end() ->scalarNode('gaufrette')->defaultFalse()->end() + ->end() + ; + + $this->addMetadataSection($root); + $this->addMappingsSection($root); + + return $tb; + } + + protected function addMetadataSection(ArrayNodeDefinition $node) + { + $node + ->children() ->arrayNode('metadata') ->addDefaultsIfNotSet() ->fixXmlConfig('directory', 'directories') @@ -50,6 +76,13 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() + ->end(); + } + + protected function addMappingsSection(ArrayNodeDefinition $node) + { + $node + ->children() ->arrayNode('mappings') ->useAttributeAsKey('id') ->prototype('array') @@ -64,9 +97,6 @@ public function getConfigTreeBuilder() ->end() ->end() ->end() - ->end() - ; - - return $tb; + ->end(); } } diff --git a/DependencyInjection/VichUploaderExtension.php b/DependencyInjection/VichUploaderExtension.php index b6f6512d..f0c9820a 100644 --- a/DependencyInjection/VichUploaderExtension.php +++ b/DependencyInjection/VichUploaderExtension.php @@ -3,6 +3,7 @@ namespace Vich\UploaderBundle\DependencyInjection; use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\Config\FileLocator; @@ -20,16 +21,8 @@ class VichUploaderExtension extends Extension * @var array $tagMap */ protected $tagMap = array( - 'orm' => 'doctrine.event_subscriber', - 'mongodb' => 'doctrine_mongodb.odm.event_subscriber' - ); - - /** - * @var array $adapterMap - */ - protected $adapterMap = array( - 'orm' => 'Vich\UploaderBundle\Adapter\ORM\DoctrineORMAdapter', - 'mongodb' => 'Vich\UploaderBundle\Adapter\ODM\MongoDB\MongoDBAdapter' + 'orm' => 'doctrine.event_subscriber', + 'mongodb' => 'doctrine_mongodb.odm.event_subscriber', ); /** @@ -47,17 +40,31 @@ public function load(array $configs, ContainerBuilder $container) } $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); - $driver = strtolower($config['db_driver']); - if (!in_array($driver, array_keys($this->tagMap))) { - throw new \InvalidArgumentException(sprintf( - 'Invalid "db_driver" configuration option specified: "%s"', - $driver - )); + $this->loadServicesFiles($container, $config); + $this->registerMetadataDirectories($container, $config); + $this->registerCacheStrategy($container, $config); + + // define a few parameters + $container->setParameter('vich_uploader.driver', $config['db_driver']); + $container->setParameter('vich_uploader.mappings', $config['mappings']); + $container->setParameter('vich_uploader.storage_service', $config['storage']); + + // choose the right listener + if ($config['db_driver'] !== 'propel') { + $container->getDefinition('vich_uploader.listener.uploader.'.$config['db_driver'])->addTag($this->tagMap[$config['db_driver']]); } + // define the adapter listener to use + $container->setAlias('vich_uploader.adapter', 'vich_uploader.adapter.'.$config['db_driver']); + + // define the event listener to use + $container->setAlias('vich_uploader.listener.uploader', 'vich_uploader.listener.uploader.'.$config['db_driver']); + } + + protected function loadServicesFiles(ContainerBuilder $container, array $config) + { $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $toLoad = array( @@ -75,16 +82,6 @@ public function load(array $configs, ContainerBuilder $container) if ($config['twig']) { $loader->load('twig.xml'); } - - $mappings = isset($config['mappings']) ? $config['mappings'] : array(); - $container->setParameter('vich_uploader.mappings', $mappings); - - $container->setParameter('vich_uploader.storage_service', $config['storage']); - $container->setParameter('vich_uploader.adapter.class', $this->adapterMap[$driver]); - $container->getDefinition('vich_uploader.listener.uploader')->addTag($this->tagMap[$driver]); - - $this->registerMetadataDirectories($container, $config); - $this->registerCacheStrategy($container, $config); } protected function registerMetadataDirectories(ContainerBuilder $container, array $config) @@ -94,7 +91,7 @@ protected function registerMetadataDirectories(ContainerBuilder $container, arra // directories $directories = array(); if ($config['metadata']['auto_detection']) { - foreach ($bundles as $name => $class) { + foreach ($bundles as $class) { $ref = new \ReflectionClass($class); $directory = dirname($ref->getFileName()).'/Resources/config/vich_uploader'; @@ -140,10 +137,8 @@ protected function registerCacheStrategy(ContainerBuilder $container, array $con ; $dir = $container->getParameterBag()->resolveValue($config['metadata']['file_cache']['dir']); - if (!file_exists($dir)) { - if (!$rs = @mkdir($dir, 0777, true)) { - throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $dir)); - } + if (!file_exists($dir) && !@mkdir($dir, 0777, true)) { + throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $dir)); } } else { $container->setAlias('vich_uploader.metadata.cache', new Alias($config['metadata']['cache'], false)); diff --git a/EventListener/DoctrineUploaderListener.php b/EventListener/DoctrineUploaderListener.php new file mode 100644 index 00000000..698028f4 --- /dev/null +++ b/EventListener/DoctrineUploaderListener.php @@ -0,0 +1,119 @@ + + */ +class DoctrineUploaderListener implements EventSubscriber +{ + /** + * @var AdapterInterface $adapter + */ + protected $adapter; + + /** + * @var MetadataReader $metadata + */ + protected $metadata; + + /** + * @var UploaderHandler $handler + */ + protected $handler; + + /** + * Constructs a new instance of UploaderListener. + * + * @param AdapterInterface $adapter The adapter. + * @param MetadataReader $metadata The metadata reader. + * @param UploaderHandler $handler The upload handler. + */ + public function __construct(AdapterInterface $adapter, MetadataReader $metadata, UploadHandler $handler) + { + $this->adapter = $adapter; + $this->metadata = $metadata; + $this->handler = $handler; + } + + /** + * The events the listener is subscribed to. + * + * @return array The array of events. + */ + public function getSubscribedEvents() + { + return array( + 'prePersist', + 'preUpdate', + 'postLoad', + 'postRemove', + ); + } + + /** + * Checks for file to upload. + * + * @param EventArgs $event The event. + */ + public function prePersist(EventArgs $event) + { + $obj = $this->adapter->getObjectFromEvent($event); + + if ($this->metadata->isUploadable($this->adapter->getClassName($obj))) { + $this->handler->handleUpload($obj); + } + } + + /** + * Update the file and file name if necessary. + * + * @param EventArgs $event The event. + */ + public function preUpdate(EventArgs $event) + { + $obj = $this->adapter->getObjectFromEvent($event); + + if ($this->metadata->isUploadable($this->adapter->getClassName($obj))) { + $this->handler->handleUpload($obj); + $this->adapter->recomputeChangeSet($event); + } + } + + /** + * Populates uploadable fields from filename properties. + * + * @param EventArgs $event The event. + */ + public function postLoad(EventArgs $event) + { + $obj = $this->adapter->getObjectFromEvent($event); + + if ($this->metadata->isUploadable($this->adapter->getClassName($obj))) { + $this->handler->handleHydration($obj); + } + } + + /** + * Removes the file if necessary. + * + * @param EventArgs $event The event. + */ + public function postRemove(EventArgs $event) + { + $obj = $this->adapter->getObjectFromEvent($event); + + if ($this->metadata->isUploadable($this->adapter->getClassName($obj))) { + $this->handler->handleDeletion($obj); + } + } +} diff --git a/EventListener/PropelUploaderListener.php b/EventListener/PropelUploaderListener.php new file mode 100644 index 00000000..7dad4a28 --- /dev/null +++ b/EventListener/PropelUploaderListener.php @@ -0,0 +1,87 @@ + + */ +class PropelUploaderListener implements EventSubscriberInterface +{ + /** + * @var AdapterInterface $adapter + */ + protected $adapter; + + /** + * @var UploaderHandler $handler + */ + protected $handler; + + /** + * Constructs a new instance of UploaderListener. + * + * @param AdapterInterface $adapter The adapter. + * @param UploaderHandler $handler The upload handler. + */ + public function __construct(AdapterInterface $adapter, UploadHandler $handler) + { + $this->adapter = $adapter; + $this->handler = $handler; + } + + /** + * The events the listener is subscribed to. + * + * @return array The array of events. + */ + public static function getSubscribedEvents() + { + return array( + 'propel.pre_insert' => 'onUpload', + 'propel.pre_update' => 'onUpload', + 'propel.post_delete' => 'onDelete', + 'propel.post_hydrate' => 'onHydrate', + ); + } + + /** + * Update the file and file name if necessary. + * + * @param GenericEvent $event The event. + */ + public function onUpload(GenericEvent $event) + { + $obj = $this->adapter->getObjectFromEvent($event); + $this->handler->handleUpload($obj); + } + + /** + * Populates uploadable fields from filename properties. + * + * @param GenericEvent $event The event. + */ + public function onHydrate(GenericEvent $event) + { + $obj = $this->adapter->getObjectFromEvent($event); + $this->handler->handleHydration($obj); + } + + /** + * Removes the file when the object is deleted. + * + * @param GenericEvent $event The event. + */ + public function onDelete(GenericEvent $event) + { + $obj = $this->adapter->getObjectFromEvent($event); + $this->handler->handleDeletion($obj); + } +} diff --git a/EventListener/UploaderListener.php b/EventListener/UploaderListener.php deleted file mode 100644 index 12e06bbf..00000000 --- a/EventListener/UploaderListener.php +++ /dev/null @@ -1,129 +0,0 @@ - - */ -class UploaderListener implements EventSubscriber -{ - /** - * @var \Vich\UploaderBundle\Adapter\AdapterInterface $adapter - */ - protected $adapter; - - /** - * @var \Vich\UploaderBundle\Mapping\MappingReader $mapping - */ - protected $mapping; - - /** - * @var \Vich\UploaderBundle\Storage\StorageInterface $storage - */ - protected $storage; - - /** - * @var \Vich\UploaderBundle\Injector\FileInjectorInterface $injector - */ - protected $injector; - - /** - * Constructs a new instance of UploaderListener. - * - * @param \Vich\UploaderBundle\Adapter\AdapterInterface $adapter The adapter. - * @param \Vich\UploaderBundle\Mapping\MappingReader $mapping The mapping reader. - * @param \Vich\UploaderBundle\Storage\StorageInterface $storage The storage. - * @param \Vich\UploaderBundle\Injector\FileInjectorInterface $injector The injector. - */ - public function __construct(AdapterInterface $adapter, MappingReader $mapping, StorageInterface $storage, FileInjectorInterface $injector) - { - $this->adapter = $adapter; - $this->mapping = $mapping; - $this->storage = $storage; - $this->injector = $injector; - } - - /** - * The events the listener is subscribed to. - * - * @return array The array of events. - */ - public function getSubscribedEvents() - { - return array( - 'prePersist', - 'preUpdate', - 'postLoad', - 'postRemove', - ); - } - - /** - * Checks for file to upload. - * - * @param \Doctrine\Common\EventArgs $args The event arguments. - */ - public function prePersist(EventArgs $args) - { - $obj = $this->adapter->getObjectFromArgs($args); - - if ($this->mapping->isUploadable($this->adapter->getReflectionClass($obj))) { - $this->storage->upload($obj); - $this->injector->injectFiles($obj); - } - } - - /** - * Update the file and file name if necessary. - * - * @param EventArgs $args The event arguments. - */ - public function preUpdate(EventArgs $args) - { - $obj = $this->adapter->getObjectFromArgs($args); - - if ($this->mapping->isUploadable($this->adapter->getReflectionClass($obj))) { - $this->storage->upload($obj); - $this->injector->injectFiles($obj); - $this->adapter->recomputeChangeSet($args); - } - } - - /** - * Populates uploadable fields from filename properties - * if necessary. - * - * @param \Doctrine\Common\EventArgs $args - */ - public function postLoad(EventArgs $args) - { - $obj = $this->adapter->getObjectFromArgs($args); - - if ($this->mapping->isUploadable($this->adapter->getReflectionClass($obj))) { - $this->injector->injectFiles($obj); - } - } - - /** - * Removes the file if necessary. - * - * @param EventArgs $args The event arguments. - */ - public function postRemove(EventArgs $args) - { - $obj = $this->adapter->getObjectFromArgs($args); - - if ($this->mapping->isUploadable($this->adapter->getReflectionClass($obj))) { - $this->storage->remove($obj); - } - } -} diff --git a/Handler/UploadHandler.php b/Handler/UploadHandler.php new file mode 100644 index 00000000..dcf9e82a --- /dev/null +++ b/Handler/UploadHandler.php @@ -0,0 +1,55 @@ + + */ +class UploadHandler +{ + /** + * @var \Vich\UploaderBundle\Storage\StorageInterface $storage + */ + protected $storage; + + /** + * @var \Vich\UploaderBundle\Injector\FileInjectorInterface $injector + */ + protected $injector; + + /** + * Constructs a new instance of UploaderListener. + * + * @param \Vich\UploaderBundle\Storage\StorageInterface $storage The storage. + * @param \Vich\UploaderBundle\Injector\FileInjectorInterface $injector The injector. + */ + public function __construct(StorageInterface $storage, FileInjectorInterface $injector) + { + $this->storage = $storage; + $this->injector = $injector; + } + + /** + * Checks for file to upload. + */ + public function handleUpload($obj) + { + $this->storage->upload($obj); + $this->injector->injectFiles($obj); + } + + public function handleHydration($obj) + { + $this->injector->injectFiles($obj); + } + + public function handleDeletion($obj) + { + $this->storage->remove($obj); + } +} diff --git a/Injector/FileInjector.php b/Injector/FileInjector.php index 1f5f8beb..17e3f823 100644 --- a/Injector/FileInjector.php +++ b/Injector/FileInjector.php @@ -2,10 +2,12 @@ namespace Vich\UploaderBundle\Injector; +use Symfony\Component\HttpFoundation\File\File; + use Vich\UploaderBundle\Injector\FileInjectorInterface; use Vich\UploaderBundle\Mapping\PropertyMappingFactory; use Vich\UploaderBundle\Storage\StorageInterface; -use Symfony\Component\HttpFoundation\File\File; + /** * FileInjector. * @@ -14,20 +16,20 @@ class FileInjector implements FileInjectorInterface { /** - * @var \Vich\UploaderBundle\Mapping\PropertyMappingFactory $factory + * @var PropertyMappingFactory $factory */ protected $factory; /** - * @var \Vich\UploaderBundle\Storage\StorageInterface + * @var StorageInterface */ protected $storage; /** * Constructs a new instance of FileInjector. * - * @param \Vich\UploaderBundle\Mapping\PropertyMappingFactory $factory The factory. - * @param \Vich\UploaderBundle\Storage\StorageInterface $storage Storage. + * @param PropertyMappingFactory $factory The factory. + * @param StorageInterface $storage Storage. */ public function __construct(PropertyMappingFactory $factory, StorageInterface $storage) { @@ -46,17 +48,14 @@ public function injectFiles($obj) continue; } - $field = $mapping->getProperty()->getName(); + $field = $mapping->getFilePropertyName(); try { $path = $this->storage->resolvePath($obj, $field); } catch (\InvalidArgumentException $e) { continue; } - $mapping->getProperty()->setValue( - $obj, - new File($path, false) - ); + $mapping->setFile($obj, new File($path, false)); } } } diff --git a/Mapping/MappingReader.php b/Mapping/MappingReader.php deleted file mode 100644 index 89752f70..00000000 --- a/Mapping/MappingReader.php +++ /dev/null @@ -1,72 +0,0 @@ - - */ -class MappingReader -{ - /** - * @var AgnosticReader $reader - */ - protected $reader; - - /** - * Constructs a new instance of the MappingReader. - * - * @param MetadataFactoryInterface $reader The metadata reader. - */ - public function __construct(MetadataFactoryInterface $reader) - { - $this->reader = $reader; - } - - /** - * Tells if the given class is uploadable. - * - * @param Reflectionclass $class The class to test. - * - * @return bool - */ - public function isUploadable(\ReflectionClass $class) - { - $metadata = $this->reader->getMetadataForClass($class->name); - - return $metadata !== null; - } - - /** - * Attempts to read the uploadable fields. - * - * @param \ReflectionClass $class The reflection class. - * - * @return array A list of uploadable fields. - */ - public function getUploadableFields(\ReflectionClass $class) - { - $metadata = $this->reader->getMetadataForClass($class->getName()); - $classMetadata = $metadata->classMetadata[$class->getName()]; - - return $classMetadata->fields; - } - - /** - * Attempts to read the mapping of a specified property. - * - * @param \ReflectionClass $class The class. - * @param string $field The field - * - * @return null|array The field mapping. - */ - public function getUploadableField(\ReflectionClass $class, $field) - { - $fieldsMetadata = $this->getUploadableFields($class); - - return isset($fieldsMetadata[$field]) ? $fieldsMetadata[$field] : null; - } -} diff --git a/Mapping/PropertyMapping.php b/Mapping/PropertyMapping.php index d0a2dfb4..03fdb985 100644 --- a/Mapping/PropertyMapping.php +++ b/Mapping/PropertyMapping.php @@ -2,6 +2,8 @@ namespace Vich\UploaderBundle\Mapping; +use Symfony\Component\PropertyAccess\PropertyAccess; + use Vich\UploaderBundle\Naming\NamerInterface; use Vich\UploaderBundle\Naming\DirectoryNamerInterface; @@ -12,16 +14,6 @@ */ class PropertyMapping { - /** - * @var \ReflectionProperty $property - */ - protected $property; - - /** - * @var \ReflectionProperty $fileNameProperty - */ - protected $fileNameProperty; - /** * @var NamerInterface $namer */ @@ -43,49 +35,33 @@ class PropertyMapping protected $mappingName; /** - * Gets the reflection property that represents the - * annotated property. - * - * @return \ReflectionProperty The property. + * @var string $filePropertyPath */ - public function getProperty() - { - return $this->property; - } + protected $filePropertyPath; /** - * Sets the reflection property that represents the annotated - * property. - * - * @param \ReflectionProperty $property The reflection property. + * @var string $fileNamePropertyPath */ - public function setProperty(\ReflectionProperty $property) - { - $this->property = $property; - $this->property->setAccessible(true); - } + protected $fileNamePropertyPath; /** - * Gets the reflection property that represents the property - * which holds the file name for the mapping. - * - * @return \ReflectionProperty The reflection property. + * @var PropertyAccess $accessor */ - public function getFileNameProperty() + protected $accessor; + + public function __construct($filePropertyPath, $fileNamePropertyPath) { - return $this->fileNameProperty; + $this->filePropertyPath = $filePropertyPath; + $this->fileNamePropertyPath = $fileNamePropertyPath; } - /** - * Sets the reflection property that represents the property - * which holds the file name for the mapping. - * - * @param \ReflectionProperty $fileNameProperty The reflection property. - */ - public function setFileNameProperty(\ReflectionProperty $fileNameProperty) + protected function getAccessor() { - $this->fileNameProperty = $fileNameProperty; - $this->fileNameProperty->setAccessible(true); + if ($this->accessor !== null) { + return $this->accessor; + } + + return $this->accessor = PropertyAccess::getPropertyAccessor(); } /** @@ -179,49 +155,65 @@ public function setMappingName($mappingName) } /** - * Gets the name of the annotated property. + * Gets the file property value for the given object. * - * @return string The name. + * @param object $obj The object. + * @return UploadedFile The file. */ - public function getPropertyName() + public function getFile($obj) { - return $this->property->getName(); + return $this->getAccessor()->getValue($obj, $this->filePropertyPath); } /** - * Gets the value of the annotated property. + * Modifies the file property value for the given object. * - * @param object $obj The object. - * @return UploadedFile The file. + * @param object $obj The object. + * @param UploadedFile $file The new file. */ - public function getPropertyValue($obj) + public function setFile($obj, $file) { - return $this->property->getValue($obj); + $this->getAccessor()->setValue($obj, $this->filePropertyPath, $file); } /** - * Gets the configured file name property name. + * Gets the fileName property of the given object. + * + * @param object $obj The object. + * @return string The filename. + */ + public function getFileName($obj) + { + return $this->getAccessor()->getValue($obj, $this->fileNamePropertyPath); + } + + /** + * Modifies the fileName property of the given object. + * + * @param object $obj The object. + */ + public function setFileName($obj, $value) + { + $this->getAccessor()->setValue($obj, $this->fileNamePropertyPath, $value); + } + + /** + * Gets the configured file property name. * * @return string The name. */ - public function getFileNamePropertyName() + public function getFilePropertyName() { - return $this->fileNameProperty->getName(); + return $this->filePropertyPath; } /** - * Gets the configured upload directory. + * Returns the raw upload destination, as given in the configuration. * - * @param null $obj - * @param null $field * @return string The configured upload directory. */ - public function getUploadDir($obj = null, $field = null) + public function getUploadDestination() { - if ($this->hasDirectoryNamer()) { - return $this->getDirectoryNamer()->directoryName($obj, $field, $this->mapping['upload_destination']); - } - return $this->mapping['upload_destination']; } diff --git a/Mapping/PropertyMappingFactory.php b/Mapping/PropertyMappingFactory.php index ecb1da67..9690804d 100644 --- a/Mapping/PropertyMappingFactory.php +++ b/Mapping/PropertyMappingFactory.php @@ -2,12 +2,13 @@ namespace Vich\UploaderBundle\Mapping; -use Vich\UploaderBundle\Mapping\MappingReader; -use Vich\UploaderBundle\Mapping\PropertyMapping; -use Vich\UploaderBundle\Adapter\AdapterInterface; +use Doctrine\Common\Persistence\Proxy; use Symfony\Component\DependencyInjection\ContainerInterface; + +use Vich\UploaderBundle\Adapter\AdapterInterface; use Vich\UploaderBundle\Mapping\Annotation\UploadableField; -use Doctrine\Common\Persistence\Proxy; +use Vich\UploaderBundle\Metadata\MetadataReader; +use Vich\UploaderBundle\Mapping\PropertyMapping; /** * PropertyMappingFactory. @@ -22,9 +23,9 @@ class PropertyMappingFactory protected $container; /** - * @var MappingReader $mapping + * @var MetadataReader $metadata */ - protected $mapping; + protected $metadata; /** * @var AdapterInterface $adapter @@ -39,15 +40,15 @@ class PropertyMappingFactory /** * Constructs a new instance of PropertyMappingFactory. * - * @param \Symfony\Component\DependencyInjection\ContainerInterface $container The container. - * @param \Vich\UploaderBundle\Mapping\MappingReader $mapping The mapping mapping. - * @param \Vich\UploaderBundle\Adapter\AdapterInterface $adapter The adapter. - * @param array $mappings The configured mappings. + * @param ContainerInterface $container The container. + * @param MetadataReader $metadata The metadata mapping. + * @param AdapterInterface $adapter The adapter. + * @param array $mappings The configured mappings. */ - public function __construct(ContainerInterface $container, MappingReader $mapping, AdapterInterface $adapter, array $mappings) + public function __construct(ContainerInterface $container, MetadataReader $metadata, AdapterInterface $adapter, array $mappings) { $this->container = $container; - $this->mapping = $mapping; + $this->metadata = $metadata; $this->adapter = $adapter; $this->mappings = $mappings; } @@ -57,20 +58,23 @@ public function __construct(ContainerInterface $container, MappingReader $mappin * configuration for the uploadable fields in the specified * object. * - * @param object $obj The object. - * @return array An array up PropertyMapping objects. + * @param object $obj The object. + * @param string $className The object's class. Mandatory if $obj can't be used to determine it. + * + * @return array An array up PropertyMapping objects. */ - public function fromObject($obj) + public function fromObject($obj, $className = null) { + // @todo nothing to do here if ($obj instanceof Proxy) { $obj->__load(); } - $class = $this->adapter->getReflectionClass($obj); + $class = $this->getClassName($obj, $className); $this->checkUploadable($class); $mappings = array(); - foreach ($this->mapping->getUploadableFields($class) as $field => $mappingData) { + foreach ($this->metadata->getUploadableFields($class) as $field => $mappingData) { $mappings[] = $this->createMapping($obj, $field, $mappingData); } @@ -81,20 +85,23 @@ public function fromObject($obj) * Creates a property mapping object which contains the * configuration for the specified uploadable field. * - * @param object $obj The object. - * @param string $field The field. + * @param object $obj The object. + * @param string $field The field. + * @param string $className The object's class. Mandatory if $obj can't be used to determine it. + * * @return null|PropertyMapping The property mapping. */ - public function fromField($obj, $field) + public function fromField($obj, $field, $className = null) { + // @todo nothing to do here if ($obj instanceof Proxy) { $obj->__load(); } - $class = $this->adapter->getReflectionClass($obj); + $class = $this->getClassName($obj, $className); $this->checkUploadable($class); - $mappingData = $this->mapping->getUploadableField($class, $field); + $mappingData = $this->metadata->getUploadableField($class, $field); if ($mappingData === null) { return null; } @@ -105,12 +112,13 @@ public function fromField($obj, $field) /** * Checks to see if the class is uploadable. * - * @param \ReflectionClass $class The class. - * @throws \InvalidArgumentException + * @param string $class The class name (FQCN). + * + * @throws InvalidArgumentException */ - protected function checkUploadable(\ReflectionClass $class) + protected function checkUploadable($class) { - if (!$this->mapping->isUploadable($class)) { + if (!$this->metadata->isUploadable($class)) { throw new \InvalidArgumentException('The object is not uploadable.'); } } @@ -118,17 +126,15 @@ protected function checkUploadable(\ReflectionClass $class) /** * Creates the property mapping from the read annotation and configured mapping. * - * @param object $obj The object. - * @param string $fieldName The field name. - * @param \Vich\UploaderBundle\Annotation\UploadableField $mappingData The mapping data. + * @param object $obj The object. + * @param string $fieldName The field name. + * @param UploadableField $mappingData The mapping data. * - * @return PropertyMapping The property mapping. - * @throws \InvalidArgumentException + * @return PropertyMapping The property mapping. + * @throws InvalidArgumentException */ protected function createMapping($obj, $fieldName, array $mappingData) { - $class = $this->adapter->getReflectionClass($obj); - if (!array_key_exists($mappingData['mapping'], $this->mappings)) { throw new \InvalidArgumentException(sprintf( 'No mapping named "%s" configured.', $mappingData['mapping'] @@ -137,9 +143,7 @@ protected function createMapping($obj, $fieldName, array $mappingData) $config = $this->mappings[$mappingData['mapping']]; - $mapping = new PropertyMapping(); - $mapping->setProperty($class->getProperty($mappingData['propertyName'] ?: $fieldName)); - $mapping->setFileNameProperty($class->getProperty($mappingData['fileNameProperty'])); + $mapping = new PropertyMapping($mappingData['propertyName'] ?: $fieldName, $mappingData['fileNameProperty']); $mapping->setMappingName($mappingData['mapping']); $mapping->setMapping($config); @@ -153,4 +157,25 @@ protected function createMapping($obj, $fieldName, array $mappingData) return $mapping; } + + /** + * Returns the className of the given object. + * + * @param object $object The object to inspect. + * @param string $className User specified className. + * + * @return string + */ + protected function getClassName($object, $className = null) + { + if ($className !== null) { + return $className; + } + + if (is_object($object)) { + return $this->adapter->getClassName($object); + } + + throw new \RuntimeException('Impossible to determine the class name. Either specify it explicitly or give an object'); + } } diff --git a/Metadata/Driver/AbstractFileDriver.php b/Metadata/Driver/AbstractFileDriver.php new file mode 100644 index 00000000..4c02c2ba --- /dev/null +++ b/Metadata/Driver/AbstractFileDriver.php @@ -0,0 +1,69 @@ + + */ +abstract class AbstractFileDriver implements AdvancedDriverInterface +{ + /** + * @var FileLocatorInterface|FileLocator + */ + private $locator; + + public function __construct(FileLocatorInterface $locator) + { + $this->locator = $locator; + } + + public function loadMetadataForClass(\ReflectionClass $class) + { + if (null === $path = $this->locator->findFileForClass($class, $this->getExtension())) { + return null; + } + + return $this->loadMetadataFromFile($path, $class); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if (!$this->locator instanceof AdvancedFileLocatorInterface) { + throw new \RuntimeException('Locator "%s" must be an instance of "AdvancedFileLocatorInterface".'); + } + + $classNames = array(); + foreach ($this->locator->findAllClasses($this->getExtension()) as $file) { + $metadata = $this->loadMetadataFromFile($file->getRealpath()); + $classNames[] = $metadata->name; + } + + return $classNames; + } + + /** + * Parses the content of the file, and converts it to the desired metadata. + * + * @param string $file + * @param \ReflectionClass $class + * + * @return \MetaData\ClassMetadata|null + */ + abstract protected function loadMetadataFromFile($file, \ReflectionClass $class = null); + + /** + * Returns the extension of the file. + * + * @return string + */ + abstract protected function getExtension(); +} diff --git a/Metadata/Driver/Chain.php b/Metadata/Driver/Chain.php new file mode 100644 index 00000000..d347fcd3 --- /dev/null +++ b/Metadata/Driver/Chain.php @@ -0,0 +1,51 @@ +drivers = $drivers; + } + + public function addDriver(DriverInterface $driver) + { + $this->drivers[] = $driver; + } + + public function loadMetadataForClass(\ReflectionClass $class) + { + foreach ($this->drivers as $driver) { + if (null !== ($metadata = $driver->loadMetadataForClass($class))) { + return $metadata; + } + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + $classes = array(); + foreach ($this->drivers as $driver) { + if (!$driver instanceof AdvancedDriverInterface) { + continue; + } + + $driverClasses = $driver->getAllClassNames(); + if (!empty($driverClasses)) { + $classes = array_merge($classes, $driverClasses); + } + } + + return $classes; + } +} diff --git a/Metadata/Driver/FileLocator.php b/Metadata/Driver/FileLocator.php index c0f0e370..28d19acf 100644 --- a/Metadata/Driver/FileLocator.php +++ b/Metadata/Driver/FileLocator.php @@ -2,10 +2,10 @@ namespace Vich\UploaderBundle\Metadata\Driver; -use Metadata\Driver\FileLocatorInterface; +use Metadata\Driver\AdvancedFileLocatorInterface; use Symfony\Component\Finder\Finder; -class FileLocator implements FileLocatorInterface +class FileLocator implements AdvancedFileLocatorInterface { private $dirs; @@ -27,12 +27,13 @@ public function getDirs() */ public function findFileForClass(\ReflectionClass $class, $extension) { + $finder = new Finder(); + foreach ($this->dirs as $prefix => $dir) { if ('' !== $prefix && 0 !== strpos($class->getNamespaceName(), $prefix)) { continue; } - $finder = new Finder(); $files = $finder->files()->in($dir)->name(sprintf('*%s*.%s', $class->getShortName(), $extension)); if (count($files) !== 1) { @@ -46,4 +47,24 @@ public function findFileForClass(\ReflectionClass $class, $extension) return null; } + + /** + * Finds all possible metadata files. + * + * @param string $extension + * + * @return array + */ + public function findAllClasses($extension) + { + $files = array(); + $finder = new Finder(); + + foreach ($this->dirs as $dir) { + $results = $finder->files()->in($dir)->name('*.' . $extension); + $files = array_merge(iterator_to_array($results), $files); + } + + return array_unique($files); + } } diff --git a/Metadata/Driver/Yaml.php b/Metadata/Driver/Yaml.php index 188ac847..616e95c1 100644 --- a/Metadata/Driver/Yaml.php +++ b/Metadata/Driver/Yaml.php @@ -2,7 +2,6 @@ namespace Vich\UploaderBundle\Metadata\Driver; -use Metadata\Driver\AbstractFileDriver; use Symfony\Component\Yaml\Yaml as YmlParser; use Vich\UploaderBundle\Metadata\ClassMetadata; @@ -17,17 +16,13 @@ class Yaml extends AbstractFileDriver /** * {@inheritDoc} */ - protected function loadMetadataFromFile(\ReflectionClass $class, $file) + protected function loadMetadataFromFile($file, \ReflectionClass $class = null) { $config = $this->loadMappingFile($file); + $className = $this->guessClassName($file, $config, $class); + $metadata = new ClassMetadata($className); - if (!isset($config[$class->name])) { - throw new \RuntimeException(sprintf('Expected metadata for class %s to be defined in %s.', $class->name, $file)); - } - - $metadata = new ClassMetadata($class->name); - - foreach ($config[$class->name] as $field => $mappingData) { + foreach ($config[$className] as $field => $mappingData) { $fieldMetadata = array( 'mapping' => $mappingData['mapping'], 'propertyName' => $field, @@ -52,4 +47,17 @@ protected function getExtension() { return 'yml'; } + + protected function guessClassName($file, array $config, \ReflectionClass $class = null) + { + if ($class === null) { + return current(array_keys($config)); + } + + if (!isset($config[$class->name])) { + throw new \RuntimeException(sprintf('Expected metadata for class %s to be defined in %s.', $class->name, $file)); + } + + return $class->name; + } } diff --git a/Metadata/MetadataReader.php b/Metadata/MetadataReader.php new file mode 100644 index 00000000..21bfd94f --- /dev/null +++ b/Metadata/MetadataReader.php @@ -0,0 +1,84 @@ + + */ +class MetadataReader +{ + /** + * @var MetadataFactoryInterface $reader + */ + protected $reader; + + /** + * Constructs a new instance of the MetadataReader. + * + * @param MetadataFactoryInterface $reader The "low-level" metadata reader. + */ + public function __construct(MetadataFactoryInterface $reader) + { + $this->reader = $reader; + } + + /** + * Tells if the given class is uploadable. + * + * @param string $class The class name to test (FQCN). + * + * @return bool + */ + public function isUploadable($class) + { + $metadata = $this->reader->getMetadataForClass($class); + + return $metadata !== null; + } + + /** + * Returns a list of uploadable classes. + * + * @return array A list of class names. + */ + public function getUploadableClasses() + { + return $this->reader->getAllClassNames(); + } + + /** + * Attempts to read the uploadable fields. + * + * @param string $class The class name (FQCN). + * + * @return array A list of uploadable fields. + */ + public function getUploadableFields($class) + { + $metadata = $this->reader->getMetadataForClass($class); + $classMetadata = $metadata->classMetadata[$class]; + + return $classMetadata->fields; + } + + /** + * Attempts to read the mapping of a specified property. + * + * @param string $class The class (FQCN). + * @param string $field The field + * + * @return null|array The field mapping. + */ + public function getUploadableField($class, $field) + { + $fieldsMetadata = $this->getUploadableFields($class); + + return isset($fieldsMetadata[$field]) ? $fieldsMetadata[$field] : null; + } +} diff --git a/Naming/DirectoryNamerInterface.php b/Naming/DirectoryNamerInterface.php index a87a9d8c..0fbdac83 100644 --- a/Naming/DirectoryNamerInterface.php +++ b/Naming/DirectoryNamerInterface.php @@ -2,8 +2,10 @@ namespace Vich\UploaderBundle\Naming; +use Vich\UploaderBundle\Mapping\PropertyMapping; + /** - * NamerInterface. + * DirectoryNamerInterface. * * @author Kevin bond */ @@ -12,10 +14,10 @@ interface DirectoryNamerInterface /** * Creates a directory name for the file being uploaded. * - * @param object $obj The object the upload is attached to. - * @param string $field The name of the uploadable field to generate a name for. - * @param string $uploadDir The upload directory set in config + * @param Propertymapping $mapping The mapping to use to manipulate the given object. + * @param object $object The object the upload is attached to. + * * @return string The directory name. */ - public function directoryName($obj, $field, $uploadDir); + public function name(PropertyMapping $mapping, $object); } diff --git a/Naming/NamerInterface.php b/Naming/NamerInterface.php index fff4b833..b00a8909 100644 --- a/Naming/NamerInterface.php +++ b/Naming/NamerInterface.php @@ -2,6 +2,8 @@ namespace Vich\UploaderBundle\Naming; +use Vich\UploaderBundle\Mapping\PropertyMapping; + /** * NamerInterface. * @@ -12,9 +14,10 @@ interface NamerInterface /** * Creates a name for the file being uploaded. * - * @param object $obj The object the upload is attached to. - * @param string $field The name of the uploadable field to generate a name for. + * @param Propertymapping $mapping The mapping to use to manipulate the given object. + * @param object $object The object the upload is attached to. + * * @return string The file name. */ - public function name($obj, $field); + public function name(PropertyMapping $mapping, $object); } diff --git a/Naming/OrignameNamer.php b/Naming/OrignameNamer.php index 5955d8de..8958594e 100644 --- a/Naming/OrignameNamer.php +++ b/Naming/OrignameNamer.php @@ -2,8 +2,7 @@ namespace Vich\UploaderBundle\Naming; -use Symfony\Component\HttpFoundation\File\File; -use Symfony\Component\HttpFoundation\File\UploadedFile; +use Vich\UploaderBundle\Mapping\PropertyMapping; /** * OrignameNamer @@ -15,16 +14,11 @@ class OrignameNamer implements NamerInterface /** * {@inheritDoc} */ - public function name($obj, $field) + public function name(PropertyMapping $mapping, $object) { - $refObj = new \ReflectionObject($obj); + $file = $mapping->getFile($object); - $refProp = $refObj->getProperty($field); - $refProp->setAccessible(true); - - $file = $refProp->getValue($obj); - - /** @var $file UploadedFile */ + /** @var $file \Symfony\Component\HttpFoundation\File\UploadedFile */ return uniqid().'_'.$file->getClientOriginalName(); } diff --git a/Naming/UniqidNamer.php b/Naming/UniqidNamer.php index 3f3b86df..234e81f1 100644 --- a/Naming/UniqidNamer.php +++ b/Naming/UniqidNamer.php @@ -3,6 +3,7 @@ namespace Vich\UploaderBundle\Naming; use Symfony\Component\HttpFoundation\File\UploadedFile; +use Vich\UploaderBundle\Mapping\PropertyMapping; /** * UniqidNamer @@ -14,14 +15,9 @@ class UniqidNamer implements NamerInterface /** * {@inheritDoc} */ - public function name($obj, $field) + public function name(PropertyMapping $mapping, $object) { - $refObj = new \ReflectionObject($obj); - - $refProp = $refObj->getProperty($field); - $refProp->setAccessible(true); - - $file = $refProp->getValue($obj); + $file = $mapping->getFile($object); $name = uniqid(); if ($extension = $this->getExtension($file)) { diff --git a/Resources/config/adapter.xml b/Resources/config/adapter.xml index af37c005..75f4f690 100644 --- a/Resources/config/adapter.xml +++ b/Resources/config/adapter.xml @@ -6,7 +6,9 @@ - + + + diff --git a/Resources/config/factory.xml b/Resources/config/factory.xml index 8e3c12ae..447ded1d 100644 --- a/Resources/config/factory.xml +++ b/Resources/config/factory.xml @@ -8,7 +8,7 @@ - + %vich_uploader.mappings% diff --git a/Resources/config/listener.xml b/Resources/config/listener.xml index 785a7d70..2b367b08 100644 --- a/Resources/config/listener.xml +++ b/Resources/config/listener.xml @@ -6,13 +6,25 @@ - - - + + + + + + + + + + + + + + + diff --git a/Resources/config/mapping.xml b/Resources/config/mapping.xml index 26141131..6a487800 100644 --- a/Resources/config/mapping.xml +++ b/Resources/config/mapping.xml @@ -20,7 +20,7 @@ - + @@ -43,7 +43,7 @@ - + diff --git a/Storage/AbstractStorage.php b/Storage/AbstractStorage.php index 62e4a936..5a2cb2cb 100644 --- a/Storage/AbstractStorage.php +++ b/Storage/AbstractStorage.php @@ -31,13 +31,31 @@ public function __construct(PropertyMappingFactory $factory) /** * Do real upload * - * @param UploadedFile $file - * @param string $dir - * @param string $name + * @param PropertyMapping $mapping The mapping representing the current object. + * @param UploadedFile $file The file being uploaded. + * @param string $destinationPath The destination path of the file. + */ + abstract protected function doUpload(PropertyMapping $mapping, UploadedFile $file, $destinationPath); + + /** + * Do real remove + * + * @param PropertyMapping $mapping The mapping representing the current object. + * @param string $path The path of the file to remove. * - * @return boolean + * @return boolean Whether the file has been removed or not. */ - abstract protected function doUpload(UploadedFile $file, $dir, $name); + abstract protected function doRemove(PropertyMapping $mapping, $path); + + /** + * Do resolve path + * + * @param string $dir + * @param string $name + * + * @return string + */ + abstract protected function doResolvePath($dir, $path); /** * {@inheritDoc} @@ -46,43 +64,46 @@ public function upload($obj) { $mappings = $this->factory->fromObject($obj); foreach ($mappings as $mapping) { - $file = $mapping->getPropertyValue($obj); + $file = $mapping->getFile($obj); if ($file === null || !($file instanceof UploadedFile)) { continue; } - if ($mapping->getDeleteOnUpdate() && $mapping->getFileNameProperty()->getValue($obj)) { - $name = $mapping->getFileNameProperty()->getValue($obj); - $dir = $mapping->getUploadDir($obj, $mapping->getProperty()->getName()); - - $this->doRemove($dir, $name); + // if there already is a file for the given object, delete it if + // needed + if ($mapping->getDeleteOnUpdate() && ($name = $mapping->getFileName($obj))) { + $this->doRemove($mapping, $name); } + // keep the original name by default + $name = $file->getClientOriginalName(); + + // but use the namer if there is one if ($mapping->hasNamer()) { - $name = $mapping->getNamer()->name($obj, $mapping->getProperty()->getName()); - } else { - $name = $file->getClientOriginalName(); + $name = $mapping->getNamer()->name($mapping, $obj); } - $dir = $mapping->getUploadDir($obj, $mapping->getProperty()->getName()); + // update the filename + $mapping->setFileName($obj, $name); + + // determine the upload directory to use + if ($mapping->hasDirectoryNamer()) { + $dir = $mapping->getDirectoryNamer()->name($mapping, $obj); + $name = $dir . DIRECTORY_SEPARATOR . $name; - $this->doUpload($file, $dir, $name); + // store the complete path in the filename + // @note: we do this because the FileInjector needs the + // directory, and the DirectoryNamer might need the File object + // to compute it + $mapping->setFileName($obj, $name); + } - $mapping->getFileNameProperty()->setValue($obj, $name); + // and finalize the upload + $this->doUpload($mapping, $file, $name); } } - /** - * Do real remove - * - * @param string $dir - * @param string $name - * - * @return boolean - */ - abstract protected function doRemove($dir, $name); - /** * {@inheritDoc} */ @@ -96,66 +117,52 @@ public function remove($obj) continue; } - $name = $mapping->getFileNameProperty()->getValue($obj); - + $name = $mapping->getFileName($obj); if (null === $name) { continue; } - $dir = $mapping->getUploadDir($obj, $mapping->getProperty()->getName()); - - $this->doRemove($dir, $name); + $this->doRemove($mapping, $name); } } - /** - * Do resolve path - * - * @param string $dir - * @param string $name - * - * @return string - */ - abstract protected function doResolvePath($dir, $name); - /** * {@inheritDoc} */ - public function resolvePath($obj, $field) + public function resolvePath($obj, $field, $className = null) { - list($mapping, $name) = $this->getFileNamePropertyValue($obj, $field); - $dir = $mapping->getUploadDir($obj, $field); + list($mapping, $name) = $this->getFileName($obj, $field, $className); - return $this->doResolvePath($dir, $name); + return $this->doResolvePath($mapping->getUploadDestination(), $name); } /** * {@inheritDoc} */ - public function resolveUri($obj, $field) + public function resolveUri($obj, $field, $className = null) { - list($mapping, $name) = $this->getFileNamePropertyValue($obj, $field); + list($mapping, $filename) = $this->getFileName($obj, $field, $className); $uriPrefix = $mapping->getUriPrefix(); - return $name ? ($uriPrefix . '/' . $name) : ''; + return $filename ? ($uriPrefix . '/' . $filename) : ''; } - protected function getFileNamePropertyValue($obj, $field) + protected function getFileName($obj, $field, $className = null) { - $mapping = $this->factory->fromField($obj, $field); + $mapping = $this->factory->fromField($obj, $field, $className); if (null === $mapping) { throw new \InvalidArgumentException(sprintf( 'Unable to find uploadable field named: "%s"', $field )); } - $value = $mapping->getFileNameProperty()->getValue($obj); - if ($value === null) { + $name = $mapping->getFileName($obj); + if ($name === null) { throw new \InvalidArgumentException(sprintf( 'Unable to get filename property value: "%s"', $field )); } - return array($mapping, $value); + return array($mapping, $name); } } diff --git a/Storage/FileSystemStorage.php b/Storage/FileSystemStorage.php index 858772d0..4e407307 100644 --- a/Storage/FileSystemStorage.php +++ b/Storage/FileSystemStorage.php @@ -4,6 +4,8 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; +use Vich\UploaderBundle\Mapping\PropertyMapping; + /** * FileSystemStorage. * @@ -14,26 +16,20 @@ class FileSystemStorage extends AbstractStorage /** * {@inheritDoc} */ - protected function doUpload(UploadedFile $file, $dir, $name) + protected function doUpload(PropertyMapping $mapping, UploadedFile $file, $destinationPath) { - $uploadDir = $this->getUploadDirectory($dir, $name); - $fileName = basename($name); + $uploadDir = $this->getUploadDirectory($mapping->getUploadDestination(), $destinationPath); + $fileName = basename($destinationPath); return $file->move($uploadDir, $fileName); } /** - * Do real remove - * - * @param string $dir - * @param string $name - * - * @internal param object $obj - * @return boolean + * {@inheritDoc} */ - protected function doRemove($dir, $name) + protected function doRemove(PropertyMapping $mapping, $path) { - $file = $dir . DIRECTORY_SEPARATOR . $name; + $file = $mapping->getUploadDestination() . DIRECTORY_SEPARATOR . $path; return file_exists($file) ? unlink($file) : false; } @@ -49,17 +45,18 @@ protected function doResolvePath($dir, $name) /** * {@inheritDoc} */ - public function resolveUri($obj, $field) + public function resolveUri($obj, $field, $className = null) { - list($mapping, $name) = $this->getFileNamePropertyValue($obj, $field); + list($mapping, $name) = $this->getFileName($obj, $field, $className); $uriPrefix = $mapping->getUriPrefix(); - $parts = explode($uriPrefix, $this->convertWindowsDirectorySeparator($mapping->getUploadDir($obj, $field))); + $parts = explode($uriPrefix, $this->convertWindowsDirectorySeparator($mapping->getUploadDestination())); - return sprintf('%s/%s', $uriPrefix . array_pop($parts), $name); + return sprintf('%s/%s', $uriPrefix . array_pop($parts), $this->convertWindowsDirectorySeparator($name)); } /** * @param $string + * * @return string */ protected function convertWindowsDirectorySeparator($string) @@ -75,6 +72,7 @@ protected function convertWindowsDirectorySeparator($string) * * @param $dir * @param $name + * * @return string */ protected function getUploadDirectory($dir, $name) diff --git a/Storage/GaufretteStorage.php b/Storage/GaufretteStorage.php index 15b1e9b8..91f08251 100644 --- a/Storage/GaufretteStorage.php +++ b/Storage/GaufretteStorage.php @@ -3,15 +3,14 @@ namespace Vich\UploaderBundle\Storage; use Gaufrette\Exception\FileNotFound; -use Vich\UploaderBundle\Mapping\PropertyMappingFactory; - -use Symfony\Component\HttpFoundation\File\UploadedFile; - -use Knp\Bundle\GaufretteBundle\FilesystemMap; - use Gaufrette\Stream\Local as LocalStream; use Gaufrette\StreamMode; use Gaufrette\Adapter\MetadataSupporter; +use Knp\Bundle\GaufretteBundle\FilesystemMap; +use Symfony\Component\HttpFoundation\File\UploadedFile; + +use Vich\UploaderBundle\Mapping\PropertyMapping; +use Vich\UploaderBundle\Mapping\PropertyMappingFactory; /** * GaufretteStorage. @@ -33,9 +32,9 @@ class GaufretteStorage extends AbstractStorage /** * Constructs a new instance of FileSystemStorage. * - * @param \Vich\UploaderBundle\Mapping\PropertyMappingFactory $factory The factory. - * @param FilesystemMap $filesystemMap Gaufrete filesystem factory. - * @param string $protocol Gaufrette stream wrapper protocol. + * @param PropertyMappingFactory $factory The factory. + * @param FilesystemMap $filesystemMap Gaufrete filesystem factory. + * @param string $protocol Gaufrette stream wrapper protocol. */ public function __construct(PropertyMappingFactory $factory, FilesystemMap $filesystemMap, $protocol = 'gaufrette') { @@ -45,31 +44,23 @@ public function __construct(PropertyMappingFactory $factory, FilesystemMap $file $this->protocol = $protocol; } - /** - * Get filesystem adapter by key - * - * @param string $key - * - * @return \Gaufrette\Filesystem - */ - protected function getFilesystem($key) - { - return $this->filesystemMap->get($key); - } - /** * {@inheritDoc} */ - protected function doUpload(UploadedFile $file, $dir, $name) + protected function doUpload(PropertyMapping $mapping, UploadedFile $file, $destinationPath) { - $filesystem = $this->getFilesystem($dir); + $fs = $this->getFilesystem($mapping->getUploadDestination()); - if ($filesystem->getAdapter() instanceof MetadataSupporter) { - $filesystem->getAdapter()->setMetadata($name, array('contentType' => $file->getMimeType())); + if ($fs->getAdapter() instanceof MetadataSupporter) { + $fs->getAdapter()->setMetadata($destinationPath, array('contentType' => $file->getMimeType())); } + // just to make sure that $dir is created before we start writing + // @todo: find a better way to do this + $fs->write($destinationPath, 'test'); + $src = new LocalStream($file->getPathname()); - $dst = $filesystem->createStream($name); + $dst = $fs->createStream($destinationPath); $src->open(new StreamMode('rb+')); $dst->open(new StreamMode('wb+')); @@ -86,12 +77,12 @@ protected function doUpload(UploadedFile $file, $dir, $name) /** * {@inheritDoc} */ - protected function doRemove($dir, $name) + protected function doRemove(PropertyMapping $mapping, $path) { - $adapter = $this->getFilesystem($dir); + $fs = $this->getFilesystem($mapping->getUploadDestination()); try { - return $adapter->delete($name); + return $fs->delete($path); } catch (FileNotFound $e) { return false; } @@ -104,4 +95,16 @@ protected function doResolvePath($dir, $name) { return $this->protocol.'://' . $dir . '/' . $name; } + + /** + * Get filesystem adapter by key + * + * @param string $key + * + * @return \Gaufrette\Filesystem + */ + protected function getFilesystem($key) + { + return $this->filesystemMap->get($key); + } } diff --git a/Storage/StorageInterface.php b/Storage/StorageInterface.php index 9fa2ef65..96770f35 100644 --- a/Storage/StorageInterface.php +++ b/Storage/StorageInterface.php @@ -10,38 +10,42 @@ interface StorageInterface { /** - * Uploads the files in the uploadable fields of the - * specified object according to the property configuration. + * Uploads the files in the uploadable fields of the specified object + * according to the property configuration. * * @param object $obj The object. */ public function upload($obj); /** - * Removes the files associated with the object if configured to - * do so. + * Removes the files associated with the object if configured to do so. * * @param object $obj The object. */ public function remove($obj); /** - * Resolves the path for a file based on the specified object - * and field name. + * Resolves the path for a file based on the specified object and field + * name. + * + * @param object $obj The object. + * @param string $field The field. + * @param string $className The object's class. Mandatory if $obj can't be + * used to determine it. * - * @param object $obj The object. - * @param string $field The field. * @return string The path. */ - public function resolvePath($obj, $field); + public function resolvePath($obj, $field, $className = null); /** - * Resolves the uri for any based on the specified object - * and field name. + * Resolves the uri for any based on the specified object and field name. + * + * @param object $obj The object. + * @param string $field The field. + * @param string $className The object's class. Mandatory if $obj can't be + * used to determine it. * - * @param object $obj The object. - * @param string $field The field. * @return string The uri. */ - public function resolveUri($obj, $field); + public function resolveUri($obj, $field, $className = null); } diff --git a/Templating/Helper/UploaderHelper.php b/Templating/Helper/UploaderHelper.php index 195d3814..72487487 100644 --- a/Templating/Helper/UploaderHelper.php +++ b/Templating/Helper/UploaderHelper.php @@ -41,12 +41,14 @@ public function getName() * Gets the public path for the file associated with the * object. * - * @param object $obj The object. - * @param string $field The field. + * @param object $obj The object. + * @param string $field The field. + * @param string $className The object's class. Mandatory if $obj can't be used to determine it. + * * @return string The public asset path. */ - public function asset($obj, $field) + public function asset($obj, $field, $className = null) { - return $this->storage->resolveUri($obj, $field); + return $this->storage->resolveUri($obj, $field, $className); } } diff --git a/Tests/Adapter/ODM/MongoDB/MongoDBAdapterTest.php b/Tests/Adapter/ODM/MongoDB/MongoDBAdapterTest.php index bf260d7b..8ca3d7f8 100644 --- a/Tests/Adapter/ODM/MongoDB/MongoDBAdapterTest.php +++ b/Tests/Adapter/ODM/MongoDB/MongoDBAdapterTest.php @@ -14,9 +14,9 @@ class MongoDBAdapterTest extends \PHPUnit_Framework_TestCase { /** - * Test the getObjectFromArgs method. + * Test the getObjectFromEvent method. */ - public function testGetObjectFromArgs() + public function testGetObjectFromEvent() { if (!class_exists('Doctrine\ODM\MongoDB\Event\LifecycleEventArgs')) { $this->markTestSkipped('Doctrine\ODM\MongoDB\Event\LifecycleEventArgs does not exist.'); @@ -33,39 +33,39 @@ public function testGetObjectFromArgs() $adapter = new MongoDBAdapter(); - $this->assertEquals($entity, $adapter->getObjectFromArgs($args)); + $this->assertEquals($entity, $adapter->getObjectFromEvent($args)); } } /** * Tests the getReflectionClass method. */ - public function testGetReflectionClass() + public function testGetClassName() { if (!interface_exists('Doctrine\ODM\MongoDB\Proxy\Proxy')) { $this->markTestSkipped('Doctrine\ODM\MongoDB\Proxy\Proxy does not exist.'); } else { $obj = new DummyEntity(); $adapter = new MongoDBAdapter(); - $class = $adapter->getReflectionClass($obj); + $class = $adapter->getClassName($obj); - $this->assertEquals($class->getName(), get_class($obj)); + $this->assertEquals('Vich\UploaderBundle\Tests\DummyEntity', $class); } } /** * Tests the getReflectionClass method with a proxy. */ - public function testGetReflectionClassProxy() + public function testGetClassNameWithProxy() { if (!interface_exists('Doctrine\ODM\MongoDB\Proxy\Proxy')) { $this->markTestSkipped('Doctrine\ODM\MongoDB\Proxy\Proxy does not exist.'); } else { $obj = new DummyEntityProxyMongo(); $adapter = new MongoDBAdapter(); - $class = $adapter->getReflectionClass($obj); + $class = $adapter->getClassName($obj); - $this->assertEquals($class->getName(), get_parent_class($obj)); + $this->assertEquals('Vich\UploaderBundle\Tests\DummyEntity', $class); } } } diff --git a/Tests/Adapter/ORM/DoctrineORMAdapterTest.php b/Tests/Adapter/ORM/DoctrineORMAdapterTest.php index 2741deac..a3d807d8 100644 --- a/Tests/Adapter/ORM/DoctrineORMAdapterTest.php +++ b/Tests/Adapter/ORM/DoctrineORMAdapterTest.php @@ -14,9 +14,9 @@ class DoctrineORMAdapterTest extends \PHPUnit_Framework_TestCase { /** - * Test the getObjectFromArgs method. + * Test the getObjectFromEvent method. */ - public function testGetObjectFromArgs() + public function testGetObjectFromEvent() { if (!class_exists('Doctrine\ORM\Event\LifecycleEventArgs')) { $this->markTestSkipped('Doctrine\ORM\Event\LifecycleEventArgs does not exist.'); @@ -33,39 +33,39 @@ public function testGetObjectFromArgs() $adapter = new DoctrineORMAdapter(); - $this->assertEquals($entity, $adapter->getObjectFromArgs($args)); + $this->assertEquals($entity, $adapter->getObjectFromEvent($args)); } } /** * Tests the getReflectionClass method. */ - public function testGetReflectionClass() + public function testGetClassName() { if (!interface_exists('Doctrine\ORM\Proxy\Proxy')) { $this->markTestSkipped('Doctrine\ORM\Proxy\Proxy does not exist.'); } else { $obj = new DummyEntity(); $adapter = new DoctrineORMAdapter(); - $class = $adapter->getReflectionClass($obj); + $class = $adapter->getClassName($obj); - $this->assertEquals($class->getName(), get_class($obj)); + $this->assertEquals('Vich\UploaderBundle\Tests\DummyEntity', $class); } } /** * Tests the getReflectionClass method with a proxy. */ - public function testGetReflectionClassProxy() + public function testGetClassNameWithProxy() { if (!interface_exists('Doctrine\ORM\Proxy\Proxy')) { $this->markTestSkipped('Doctrine\ORM\Proxy\Proxy does not exist.'); } else { $obj = new DummyEntityProxyORM(); $adapter = new DoctrineORMAdapter(); - $class = $adapter->getReflectionClass($obj); + $class = $adapter->getClassName($obj); - $this->assertEquals($class->getName(), get_parent_class($obj)); + $this->assertEquals('Vich\UploaderBundle\Tests\DummyEntity', $class); } } } diff --git a/Tests/Adapter/Propel/PropelAdapterTest.php b/Tests/Adapter/Propel/PropelAdapterTest.php new file mode 100644 index 00000000..5987e71f --- /dev/null +++ b/Tests/Adapter/Propel/PropelAdapterTest.php @@ -0,0 +1,35 @@ + + */ +class PropelAdapterTest extends \PHPUnit_Framework_TestCase +{ + public function testGetObjectFromEvent() + { + $event = $this->getMock('\Symfony\Component\EventDispatcher\GenericEvent'); + $event + ->expects($this->once()) + ->method('getSubject') + ->will($this->returnValue(42)); + + $adapter = new PropelAdapter(); + $this->assertSame(42, $adapter->getObjectFromEvent($event)); + } + + public function testGetClassName() + { + $adapter = new PropelAdapter(); + + $obj = new \DateTime(); + $class = $adapter->getClassName($obj); + + $this->assertSame('DateTime', $class); + } +} diff --git a/Tests/EventListener/DoctrineUploaderListenerTest.php b/Tests/EventListener/DoctrineUploaderListenerTest.php new file mode 100644 index 00000000..b1073496 --- /dev/null +++ b/Tests/EventListener/DoctrineUploaderListenerTest.php @@ -0,0 +1,289 @@ + + */ +class DoctrineUploaderListenerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Vich\UploaderBundle\Adapter\AdapterInterface $adapter + */ + protected $adapter; + + /** + * @var \Vich\UploaderBundle\Metadata\MetadataReader $metadata + */ + protected $metadata; + + /** + * @var \Vich\UploaderBundle\Handler\UploadHandler $handler + */ + protected $handler; + + /** + * @var EventArgs + */ + protected $event; + + /** + * @var DummyEntity + */ + protected $object; + + /** + * @var DoctrineUploaderListener + */ + protected $listener; + + /** + * Sets up the test + */ + public function setUp() + { + $this->adapter = $this->getAdapterMock(); + $this->metadata = $this->getMetadataReaderMock(); + $this->handler = $this->getHandlerMock(); + $this->object = new DummyEntity(); + $this->event = $this->getEventMock(); + + // the adapter is always used to return the object + $this->adapter + ->expects($this->any()) + ->method('getObjectFromEvent') + ->with($this->event) + ->will($this->returnValue($this->object)); + + // in these tests, the adapter is always used with the same object + $this->adapter + ->expects($this->any()) + ->method('getClassName') + ->will($this->returnValue(get_class($this->object))); + + $this->listener = new DoctrineUploaderListener($this->adapter, $this->metadata, $this->handler); + } + + /** + * Test the getSubscribedEvents method. + */ + public function testGetSubscribedEvents() + { + $events = $this->listener->getSubscribedEvents(); + + $this->assertContains('prePersist', $events); + $this->assertContains('preUpdate', $events); + $this->assertContains('postLoad', $events); + $this->assertContains('postRemove', $events); + } + + /** + * Tests the prePersist method. + */ + public function testPrePersist() + { + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(true)); + + $this->handler + ->expects($this->once()) + ->method('handleUpload') + ->with($this->object); + + $this->listener->prePersist($this->event); + } + + /** + * Tests that prePersist skips non-uploadable entity. + */ + public function testPrePersistSkipsNonUploadable() + { + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(false)); + + $this->handler + ->expects($this->never()) + ->method('handleUpload'); + + $this->listener->prePersist($this->event); + } + + /** + * Test the preUpdate method. + */ + public function testPreUpdate() + { + $this->adapter + ->expects($this->once()) + ->method('recomputeChangeSet') + ->with($this->event); + + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(true)); + + $this->handler + ->expects($this->once()) + ->method('handleUpload') + ->with($this->object); + + $this->listener->preUpdate($this->event); + } + + /** + * Test that preUpdate skips non uploadable entity. + */ + public function testPreUpdateSkipsNonUploadable() + { + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(false)); + + $this->adapter + ->expects($this->never()) + ->method('recomputeChangeSet'); + + $this->handler + ->expects($this->never()) + ->method('handleUpload'); + + $this->listener->preUpdate($this->event); + } + + /** + * Test the postLoad method. + */ + public function testPostLoad() + { + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(true)); + + $this->handler + ->expects($this->once()) + ->method('handleHydration') + ->with($this->object); + + $this->listener->postLoad($this->event); + } + + /** + * Test that postLoad skips non uploadable entity. + */ + public function testPostLoadSkipsNonUploadable() + { + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(false)); + + $this->handler + ->expects($this->never()) + ->method('handleHydration'); + + $this->listener->postLoad($this->event); + } + + /** + * Test the postRemove method. + */ + public function testPostRemove() + { + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(true)); + + $this->handler + ->expects($this->once()) + ->method('handleDeletion') + ->with($this->object); + + $this->listener->postRemove($this->event); + } + + /** + * Test that postRemove skips non uploadable entity. + */ + public function testPostRemoveSkipsNonUploadable() + { + $this->metadata + ->expects($this->once()) + ->method('isUploadable') + ->with('Vich\UploaderBundle\Tests\DummyEntity') + ->will($this->returnValue(false)); + + $this->handler + ->expects($this->never()) + ->method('handleDeletion'); + + $this->listener->postRemove($this->event); + } + + /** + * Creates a mock adapter. + * + * @return \Vich\UploaderBundle\Adapter\AdapterInterface The mock adapter. + */ + protected function getAdapterMock() + { + return $this->getMockBuilder('Vich\UploaderBundle\Adapter\AdapterInterface') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * Creates a mock metadata reader. + * + * @return \Vich\UploaderBundle\Metadata\MetadataReader The mock metadata reader. + */ + protected function getMetadataReaderMock() + { + return $this->getMockBuilder('Vich\UploaderBundle\Metadata\MetadataReader') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * Creates a mock handler. + * + * @return \Vich\UploaderBundle\Handler\UploadHandler The handler mock. + */ + protected function getHandlerMock() + { + return $this->getMockBuilder('Vich\UploaderBundle\Handler\UploadHandler') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * Creates a mock doctrine event + * + * @return \Doctrine\Common\EventArgs + */ + protected function getEventMock() + { + return $this->getMockBuilder('Doctrine\Common\EventArgs') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/Tests/EventListener/PropelUploaderListenerTest.php b/Tests/EventListener/PropelUploaderListenerTest.php new file mode 100644 index 00000000..f92e5f8c --- /dev/null +++ b/Tests/EventListener/PropelUploaderListenerTest.php @@ -0,0 +1,142 @@ + + */ +class PropelUploaderListenerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Vich\UploaderBundle\Adapter\AdapterInterface $adapter + */ + protected $adapter; + + /** + * @var \Vich\UploaderBundle\Handler\UploadHandler $handler + */ + protected $handler; + + /** + * @var PropelUploaderListener $listener + */ + protected $listener; + + /** + * Sets up the test + */ + public function setUp() + { + $this->adapter = $this->getAdapterMock(); + $this->handler = $this->getHandlerMock(); + $this->listener = new PropelUploaderListener($this->adapter, $this->handler); + } + + /** + * Test the getSubscribedEvents method. + */ + public function testGetSubscribedEvents() + { + $events = $this->listener->getSubscribedEvents(); + + $this->assertArrayHasKey('propel.pre_insert', $events); + $this->assertArrayHasKey('propel.pre_update', $events); + $this->assertArrayHasKey('propel.post_delete', $events); + $this->assertArrayHasKey('propel.post_hydrate', $events); + } + + public function testOnUpload() + { + $obj = new DummyEntity(); + $event = $this->getEventMock(); + + $this->adapter + ->expects($this->once()) + ->method('getObjectFromEvent') + ->will($this->returnValue($obj)); + + $this->handler + ->expects($this->once()) + ->method('handleUpload') + ->with($obj); + + $this->listener->onUpload($event); + } + + public function testOnConstruct() + { + $obj = new DummyEntity(); + $event = $this->getEventMock(); + + $this->adapter + ->expects($this->once()) + ->method('getObjectFromEvent') + ->will($this->returnValue($obj)); + + $this->handler + ->expects($this->once()) + ->method('handleHydration') + ->with($obj); + + $this->listener->onHydrate($event); + } + + public function testOnDelete() + { + $obj = new DummyEntity(); + $event = $this->getEventMock(); + + $this->adapter + ->expects($this->once()) + ->method('getObjectFromEvent') + ->will($this->returnValue($obj)); + + $this->handler + ->expects($this->once()) + ->method('handleDeletion') + ->with($obj); + + $this->listener->onDelete($event); + } + + /** + * Creates a mock adapter. + * + * @return \Vich\UploaderBundle\Adapter\AdapterInterface The mock adapter. + */ + protected function getAdapterMock() + { + return $this->getMockBuilder('Vich\UploaderBundle\Adapter\AdapterInterface') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * Creates a mock handler. + * + * @return \Vich\UploaderBundle\Handler\UploadHandler The handler mock. + */ + protected function getHandlerMock() + { + return $this->getMockBuilder('Vich\UploaderBundle\Handler\UploadHandler') + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * Creates a mock event. + * + * @return \Symfony\Component\EventDispatcher\GenericEvent The mock event. + */ + protected function getEventMock() + { + return $this->getMockBuilder('\Symfony\Component\EventDispatcher\GenericEvent') + ->disableOriginalConstructor() + ->getMock(); + } +} diff --git a/Tests/EventListener/UploaderListenerTest.php b/Tests/EventListener/UploaderListenerTest.php deleted file mode 100644 index 885d1bb6..00000000 --- a/Tests/EventListener/UploaderListenerTest.php +++ /dev/null @@ -1,426 +0,0 @@ - - */ -class UploaderListenerTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Vich\UploaderBundle\Adapter\AdapterInterface $adapter - */ - protected $adapter; - - /** - * @var \Vich\UploaderBundle\Mapping\MappingReader $mapping - */ - protected $mapping; - - /** - * @var \Vich\UploaderBundle\Storage\StorageInterface $storage - */ - protected $storage; - - /** - * @var \Vich\UploaderBundle\Injector\FileInjectorInterface $injector - */ - protected $injector; - - /** - * Sets up the test - */ - public function setUp() - { - $this->adapter = $this->getAdapterMock(); - $this->mapping = $this->getMappingMock(); - $this->storage = $this->getStorageMock(); - $this->injector = $this->getInjectorMock(); - } - - /** - * Test the getSubscribedEvents method. - */ - public function testGetSubscribedEvents() - { - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $events = $listener->getSubscribedEvents(); - - $this->assertTrue(in_array('prePersist', $events)); - $this->assertTrue(in_array('preUpdate', $events)); - $this->assertTrue(in_array('postLoad', $events)); - $this->assertTrue(in_array('postRemove', $events)); - } - - /** - * Tests the prePersist method. - */ - public function testPrePersist() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(true)); - - $this->storage - ->expects($this->once()) - ->method('upload') - ->with($obj); - - $this->injector - ->expects($this->once()) - ->method('injectFiles') - ->with($obj); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->prePersist($args); - } - - /** - * Tests that prePersist skips non-uploadable entity. - */ - public function testPrePersistSkipsNonUploadable() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(false)); - - $this->storage - ->expects($this->never()) - ->method('upload'); - - $this->injector - ->expects($this->never()) - ->method('injectFiles'); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->prePersist($args); - } - - /** - * Test the preUpdate method. - */ - public function testPreUpdate() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->adapter - ->expects($this->once()) - ->method('recomputeChangeSet') - ->with($args); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(true)); - - $this->storage - ->expects($this->once()) - ->method('upload') - ->with($obj); - - $this->injector - ->expects($this->once()) - ->method('injectFiles') - ->with($obj); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->preUpdate($args); - } - - /** - * Test that preUpdate skips non uploadable entity. - */ - public function testPreUpdateSkipsNonUploadable() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(false)); - - $this->storage - ->expects($this->never()) - ->method('upload'); - - $this->adapter - ->expects($this->never()) - ->method('recomputeChangeSet'); - - $this->injector - ->expects($this->never()) - ->method('injectFiles'); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->preUpdate($args); - } - - /** - * Test the postLoad method. - */ - public function testPostLoad() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(true)); - - $this->injector - ->expects($this->once()) - ->method('injectFiles') - ->with($obj); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->postLoad($args); - } - - /** - * Test that postLoad skips non uploadable entity. - */ - public function testPostLoadSkipsNonUploadable() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(false)); - - $this->injector - ->expects($this->never()) - ->method('injectFiles'); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->postLoad($args); - } - - /** - * Test the postRemove method. - */ - public function testPostRemove() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(true)); - - $this->storage - ->expects($this->once()) - ->method('remove') - ->with($obj); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->postRemove($args); - } - - /** - * Test that postRemove skips non uploadable entity. - */ - public function testPostRemoveSkipsNonUploadable() - { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - - $args = $this->getMockBuilder('Doctrine\Common\EventArgs') - ->disableOriginalConstructor() - ->getMock(); - - $this->adapter - ->expects($this->once()) - ->method('getObjectFromArgs') - ->will($this->returnValue($obj)); - - $this->adapter - ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); - - $this->mapping - ->expects($this->once()) - ->method('isUploadable') - ->with($class) - ->will($this->returnValue(false)); - - $this->storage - ->expects($this->never()) - ->method('remove'); - - $listener = new UploaderListener($this->adapter, $this->mapping, $this->storage, $this->injector); - $listener->postRemove($args); - } - - /** - * Creates a mock adapter. - * - * @return \Vich\UploaderBundle\Adapter\AdapterInterface The mock adapter. - */ - protected function getAdapterMock() - { - return $this->getMockBuilder('Vich\UploaderBundle\Adapter\AdapterInterface') - ->disableOriginalConstructor() - ->getMock(); - } - - /** - * Creates a mock mapping reader. - * - * @return \Vich\UploaderBundle\Mapping\MappingReader The mock mapping reader. - */ - protected function getMappingMock() - { - return $this->getMockBuilder('Vich\UploaderBundle\Mapping\MappingReader') - ->disableOriginalConstructor() - ->getMock(); - } - - /** - * Creates a mock storage. - * - * @return \Vich\UploaderBundle\Storage\StorageInterface The mock storage. - */ - protected function getStorageMock() - { - return $this->getMockBuilder('Vich\UploaderBundle\Storage\StorageInterface') - ->disableOriginalConstructor() - ->getMock(); - } - - /** - * Creates a mock injector. - * - * @return \Vich\UploaderBundle\Injector\FileInjectorInterface The mock injector. - */ - protected function getInjectorMock() - { - return $this->getMockBuilder('Vich\UploaderBundle\Injector\FileInjectorInterface') - ->disableOriginalConstructor() - ->getMock(); - } -} diff --git a/Tests/Injector/FileInjectorTest.php b/Tests/Injector/FileInjectorTest.php index 38d8017f..87e8c1dc 100644 --- a/Tests/Injector/FileInjectorTest.php +++ b/Tests/Injector/FileInjectorTest.php @@ -2,29 +2,39 @@ namespace Vich\UploaderBundle\Tests\Injector; -use Vich\UploaderBundle\Injector\FileInjector; +use org\bovigo\vfs\vfsStream; use Symfony\Component\HttpFoundation\File\File; + +use Vich\UploaderBundle\Injector\FileInjector; use Vich\UploaderBundle\Tests\DummyEntity; /** * FileInjectorTest. * - * @todo use vfsStream (http://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mocking-the-filesystem) - * * @author Dustin Dobervich */ class FileInjectorTest extends \PHPUnit_Framework_TestCase { /** - * @var Vich\UploaderBundle\Mapping\PropertyMappingFactory $factory + * @var \Vich\UploaderBundle\Mapping\PropertyMappingFactory $factory */ protected $factory; /** - * @var Vich\UploaderBundle\Storage\GaufretteStorage $storage + * @var \Vich\UploaderBundle\Storage\StorageInterface $storage */ protected $storage; + /** + * @var FileInjector + */ + protected $injector; + + /** + * @var vfsStreamDirectory + */ + protected $root; + /** * Sets up the test. */ @@ -32,6 +42,14 @@ public function setUp() { $this->factory = $this->getMockMappingFactory(); $this->storage = $this->getMockStorage(); + $this->root = vfsStream::setup('vich_uploader_bundle', null, array( + 'uploads' => array( + 'file.txt' => 'some content', + 'image.png' => 'some content', + ), + )); + + $this->injector = new FileInjector($this->factory, $this->storage); } /** @@ -39,31 +57,24 @@ public function setUp() */ public function testInjectsOneFile() { - $uploadDir = __DIR__ . '/..'; - $name = 'file.txt'; - - file_put_contents(sprintf('%s/%s', $uploadDir, $name), ''); - + $filePropertyName = 'file'; $obj = $this->getMock('Vich\UploaderBundle\Tests\DummyEntity'); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('setValue'); - - $fileMapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') - ->disableOriginalConstructor() - ->getMock(); + $fileMapping = $this->getPropertyMappingMock(); $fileMapping ->expects($this->once()) ->method('getInjectOnLoad') ->will($this->returnValue(true)); $fileMapping - ->expects($this->exactly(2)) - ->method('getProperty') - ->will($this->returnValue($prop)); + ->expects($this->once()) + ->method('getFilePropertyName') + ->will($this->returnValue($filePropertyName)); + $fileMapping + ->expects($this->once()) + ->method('setFile') + ->with($this->equalTo($obj), $this->callback(function ($file) { + return $file instanceof File; + })); $this->factory ->expects($this->once()) @@ -74,12 +85,10 @@ public function testInjectsOneFile() $this->storage ->expects($this->once()) ->method('resolvePath') - ->will($this->returnValue($uploadDir)); + ->with($this->equalTo($obj), $this->equalTo($filePropertyName)) + ->will($this->returnValue($this->getUploadDir() . DIRECTORY_SEPARATOR . 'file.txt')); - $inject = new FileInjector($this->factory, $this->storage); - $inject->injectFiles($obj); - - unlink(sprintf('%s/%s', $uploadDir, $name)); + $this->injector->injectFiles($obj); } /** @@ -87,52 +96,31 @@ public function testInjectsOneFile() */ public function testInjectTwoFiles() { - $uploadDir = __DIR__ . '/..'; - $fileName = 'file.txt'; - $imageName = 'image.txt'; - - file_put_contents(sprintf('%s/%s', $uploadDir, $fileName), ''); - file_put_contents(sprintf('%s/%s', $uploadDir, $imageName), ''); - $obj = $this->getMock('Vich\UploaderBundle\Tests\TwoFieldsDummyEntity'); - $fileProp = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $fileProp - ->expects($this->once()) - ->method('setValue'); - - $imageProp = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $imageProp - ->expects($this->once()) - ->method('setValue'); - - $fileMapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') - ->disableOriginalConstructor() - ->getMock(); + $fileMapping = $this->getPropertyMappingMock(); $fileMapping ->expects($this->once()) - ->method('getInjectOnLoad') - ->will($this->returnValue(true)); + ->method('getFilePropertyName') + ->will($this->returnValue('file')); $fileMapping - ->expects($this->exactly(2)) - ->method('getProperty') - ->will($this->returnValue($fileProp)); + ->expects($this->once()) + ->method('setFile') + ->with($this->equalTo($obj), $this->callback(function ($file) { + return $file instanceof File; + })); - $imageMapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') - ->disableOriginalConstructor() - ->getMock(); + $imageMapping = $this->getPropertyMappingMock(); $imageMapping ->expects($this->once()) - ->method('getInjectOnLoad') - ->will($this->returnValue(true)); + ->method('getFilePropertyName') + ->will($this->returnValue('image')); $imageMapping - ->expects($this->exactly(2)) - ->method('getProperty') - ->will($this->returnValue($imageProp)); + ->expects($this->once()) + ->method('setFile') + ->with($this->equalTo($obj), $this->callback(function ($file) { + return $file instanceof File; + })); $this->factory ->expects($this->once()) @@ -143,13 +131,12 @@ public function testInjectTwoFiles() $this->storage ->expects($this->exactly(2)) ->method('resolvePath') - ->will($this->returnValue($uploadDir)); - - $inject = new FileInjector($this->factory, $this->storage); - $inject->injectFiles($obj); + ->will($this->onConsecutiveCalls( + $this->getUploadDir() . DIRECTORY_SEPARATOR . 'file.txt', + $this->getUploadDir() . DIRECTORY_SEPARATOR . 'image.png' + )); - unlink(sprintf('%s/%s', $uploadDir, $fileName)); - unlink(sprintf('%s/%s', $uploadDir, $imageName)); + $this->injector->injectFiles($obj); } /** @@ -160,14 +147,10 @@ public function testInjectionIsSkippedIfNotConfigured() { $obj = $this->getMock('Vich\UploaderBundle\Tests\DummyEntity'); - $fileMapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') - ->disableOriginalConstructor() - ->getMock(); - + $fileMapping = $this->getPropertyMappingMock(false); $fileMapping - ->expects($this->once()) - ->method('getInjectOnLoad') - ->will($this->returnValue(false)); + ->expects($this->never()) + ->method('setFile'); $this->factory ->expects($this->once()) @@ -175,48 +158,21 @@ public function testInjectionIsSkippedIfNotConfigured() ->with($obj) ->will($this->returnValue(array($fileMapping))); - $inject = new FileInjector($this->factory, $this->storage); - $inject->injectFiles($obj); - - $this->assertEquals(null, $obj->getFile()); + $this->injector->injectFiles($obj); } - /** - * Test that if the file name property returns a null value - * then no file is injected. - */ public function testPropertyIsNullWhenFileNamePropertyIsNull() { - $uploadDir = __DIR__ . '/..'; - $obj = $this->getMock('Vich\UploaderBundle\Tests\DummyEntity'); - $fileMapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') - ->disableOriginalConstructor() - ->getMock(); - + $fileMapping = $this->getPropertyMappingMock(); $fileMapping ->expects($this->once()) - ->method('getInjectOnLoad') - ->will($this->returnValue(true)); - - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - - $prop - ->expects($this->once()) - ->method('getName') - ->will($this->returnValue('test_adapter')); - - $prop - ->expects($this->once()) - ->method('setValue'); - + ->method('getFilePropertyName') + ->will($this->returnValue('file')); $fileMapping - ->expects($this->exactly(2)) - ->method('getProperty') - ->will($this->returnValue($prop)); + ->expects($this->never()) + ->method('setFile'); $this->factory ->expects($this->once()) @@ -227,36 +183,56 @@ public function testPropertyIsNullWhenFileNamePropertyIsNull() $this->storage ->expects($this->once()) ->method('resolvePath') - ->will($this->returnValue($uploadDir)); - - $inject = new FileInjector($this->factory, $this->storage); - $inject->injectFiles($obj); + ->will($this->throwException(new \InvalidArgumentException)); - $this->assertEquals(null, $obj->getFile()); + $this->injector->injectFiles($obj); } /** * Gets a mock mapping factory. * - * @return Vich\UploaderBundle\Mapping\PropertyMappingFactory The factory. + * @return \Vich\UploaderBundle\Mapping\PropertyMappingFactory The factory. */ protected function getMockMappingFactory() { return $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMappingFactory') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * Gets a mocked property mapping. + * + * @return \Vich\UploaderBundle\Mapping\PropertyMapping The property. + */ + protected function getPropertyMappingMock($injectOnLoadEnabled = true) + { + $mapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') + ->disableOriginalConstructor() + ->getMock(); + + $mapping + ->expects($this->once()) + ->method('getInjectOnLoad') + ->will($this->returnValue($injectOnLoadEnabled)); + + return $mapping; } /** * Gets a mock storage. * - * @return Vich\UploaderBundle\Storage\GaufretteStorage Storage + * @return \Vich\UploaderBundle\Storage\StorageInterface */ protected function getMockStorage() { - return $this->getMockBuilder('Vich\UploaderBundle\Storage\GaufretteStorage') - ->disableOriginalConstructor() - ->getMock() - ; + return $this->getMockBuilder('Vich\UploaderBundle\Storage\StorageInterface') + ->disableOriginalConstructor() + ->getMock(); + } + + protected function getUploadDir() + { + return $this->root->url() . DIRECTORY_SEPARATOR . 'uploads'; } } diff --git a/Tests/Mapping/PropertyMappingFactoryTest.php b/Tests/Mapping/PropertyMappingFactoryTest.php index 83eae6fe..29eee296 100644 --- a/Tests/Mapping/PropertyMappingFactoryTest.php +++ b/Tests/Mapping/PropertyMappingFactoryTest.php @@ -18,84 +18,91 @@ class PropertyMappingFactoryTest extends \PHPUnit_Framework_TestCase protected $container; /** - * @var \Vich\UploaderBundle\Mapping\MappingReader $mapping + * @var \Vich\UploaderBundle\Metadata\MetadataReader $metadata */ - protected $mapping; + protected $metadata; /** * @var \Vich\UploaderBundle\Adapter\AdapterInterface $adapter */ protected $adapter; + /** + * @var DummyEntity + */ + protected $object; + /** * Sets up the test. */ public function setUp() { $this->container = $this->getContainerMock(); - $this->mapping = $this->getMappingMock(); + $this->metadata = $this->getMetadataReaderMock(); $this->adapter = $this->getAdapterMock(); + + $this->object = new DummyEntity(); } /** - * Tests that an exception is thrown if a non uploadable - * object is passed in. + * Tests that an exception is thrown if a non uploadable object is passed. * - * @expectedException \InvalidArgumentException + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The object is not uploadable. */ public function testFromObjectThrowsExceptionIfNotUploadable() { - $obj = new \StdClass(); - $this->adapter ->expects($this->once()) - ->method('getReflectionClass') - ->will($this->returnValue(new \ReflectionClass($obj))); + ->method('getClassName') + ->will($this->returnValue('stdClass')); - $this->mapping + $this->metadata ->expects($this->once()) ->method('isUploadable') ->will($this->returnValue(false)); - $factory = new PropertyMappingFactory($this->container, $this->mapping, $this->adapter, array()); - $factory->fromObject($obj); + $factory = new PropertyMappingFactory($this->container, $this->metadata, $this->adapter, array()); + $factory->fromObject(new \stdClass()); } /** - * Test the fromObject method with one uploadable - * field. + * @dataProvider uploadableClassNameProvider */ - public function testFromObjectOneField() + public function testFromObjectWithValidMapping($givenClassName, $inferredClassName) { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - $mappings = array( 'dummy_file' => array( - 'upload_destination' => 'images', - 'delete_on_remove' => true, - 'delete_on_update' => true, - 'namer' => null, - 'inject_on_load' => true, - 'directory_namer' => null + 'upload_destination' => 'images', + 'delete_on_remove' => true, + 'delete_on_update' => true, + 'namer' => null, + 'inject_on_load' => true, + 'directory_namer' => null ) ); - $this->adapter - ->expects($this->any()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); + if ($givenClassName === null) { + $this->adapter + ->expects($this->once()) + ->method('getClassName') + ->will($this->returnValue($inferredClassName)); + } else { + $this->adapter + ->expects($this->never()) + ->method('getClassName'); + } - $this->mapping + $this->metadata ->expects($this->once()) ->method('isUploadable') - ->with($class) + ->with($inferredClassName) ->will($this->returnValue(true)); - $this->mapping + $this->metadata ->expects($this->once()) ->method('getUploadableFields') - ->with($class) + ->with($inferredClassName) ->will($this->returnValue(array( 'file' => array( 'mapping' => 'dummy_file', @@ -104,51 +111,60 @@ public function testFromObjectOneField() ) ))); - $factory = new PropertyMappingFactory($this->container, $this->mapping, $this->adapter, $mappings); - $mappings = $factory->fromObject($obj); + $factory = new PropertyMappingFactory($this->container, $this->metadata, $this->adapter, $mappings); + $mappings = $factory->fromObject($this->object, $givenClassName); $this->assertEquals(1, count($mappings)); $mapping = $mappings[0]; $this->assertEquals('dummy_file', $mapping->getMappingName()); - $this->assertEquals('images', $mapping->getUploadDir()); + $this->assertEquals('images', $mapping->getUploadDestination()); $this->assertNull($mapping->getNamer()); $this->assertFalse($mapping->hasNamer()); $this->assertTrue($mapping->getDeleteOnRemove()); $this->assertTrue($mapping->getInjectOnLoad()); } + public function uploadableClassNameProvider() + { + $uploadableClassName = 'Vich\UploaderBundle\Tests\DummyEntity'; + + return array( + // given className, inferred className + array( null, $uploadableClassName), + array( $uploadableClassName, $uploadableClassName), + ); + } + /** * Test that an exception is thrown when an invalid mapping name * is specified. * - * @expectedException \InvalidArgumentException + * @expectedException InvalidArgumentException + * @expectedExceptionMessage No mapping named "dummy_file" configured. */ public function testThrowsExceptionOnInvalidMappingName() { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - $mappings = array( 'bad_name' => array() ); $this->adapter - ->expects($this->any()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); + ->expects($this->once()) + ->method('getClassName') + ->will($this->returnValue('Vich\UploaderBundle\Tests\DummyEntity')); - $this->mapping + $this->metadata ->expects($this->once()) ->method('isUploadable') - ->with($class) + ->with('Vich\UploaderBundle\Tests\DummyEntity') ->will($this->returnValue(true)); - $this->mapping + $this->metadata ->expects($this->once()) ->method('getUploadableFields') - ->with($class) + ->with('Vich\UploaderBundle\Tests\DummyEntity') ->will($this->returnValue(array( 'file' => array( 'mapping' => 'dummy_file', @@ -157,55 +173,49 @@ public function testThrowsExceptionOnInvalidMappingName() ) ))); - $factory = new PropertyMappingFactory($this->container, $this->mapping, $this->adapter, $mappings); - $mappings = $factory->fromObject($obj); + $factory = new PropertyMappingFactory($this->container, $this->metadata, $this->adapter, $mappings); + $factory->fromObject($this->object); } /** - * Test that the fromField method returns null when an invalid - * field name is specified. + * Test that the fromField method returns null when an invalid field name + * is specified. */ public function testFromFieldReturnsNullOnInvalidFieldName() { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - $this->adapter - ->expects($this->any()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); + ->expects($this->once()) + ->method('getClassName') + ->will($this->returnValue('Vich\UploaderBundle\Tests\DummyEntity')); - $this->mapping + $this->metadata ->expects($this->once()) ->method('isUploadable') - ->with($class) + ->with('Vich\UploaderBundle\Tests\DummyEntity') ->will($this->returnValue(true)); - $this->mapping + $this->metadata ->expects($this->once()) ->method('getUploadableField') - ->with($class) + ->with('Vich\UploaderBundle\Tests\DummyEntity') ->will($this->returnValue(null)); - $factory = new PropertyMappingFactory($this->container, $this->mapping, $this->adapter, array()); - $mapping = $factory->fromField($obj, 'oops'); + $factory = new PropertyMappingFactory($this->container, $this->metadata, $this->adapter, array()); + $mapping = $factory->fromField($this->object, 'oops'); $this->assertNull($mapping); } public function testConfiguredNamerRetrievedFromContainer() { - $obj = new DummyEntity(); - $class = new \ReflectionClass($obj); - $mappings = array( 'dummy_file' => array( - 'upload_destination' => 'images', - 'delete_on_remove' => true, - 'delete_on_update' => true, - 'namer' => 'my.custom.namer', - 'inject_on_load' => true, - 'directory_namer' => null + 'upload_destination' => 'images', + 'delete_on_remove' => true, + 'delete_on_update' => true, + 'namer' => 'my.custom.namer', + 'inject_on_load' => true, + 'directory_namer' => null ) ); @@ -218,20 +228,20 @@ public function testConfiguredNamerRetrievedFromContainer() ->will($this->returnValue($namer)); $this->adapter - ->expects($this->any()) - ->method('getReflectionClass') - ->will($this->returnValue($class)); + ->expects($this->once()) + ->method('getClassName') + ->will($this->returnValue('Vich\UploaderBundle\Tests\DummyEntity')); - $this->mapping + $this->metadata ->expects($this->once()) ->method('isUploadable') - ->with($class) + ->with('Vich\UploaderBundle\Tests\DummyEntity') ->will($this->returnValue(true)); - $this->mapping + $this->metadata ->expects($this->once()) ->method('getUploadableFields') - ->with($class) + ->with('Vich\UploaderBundle\Tests\DummyEntity') ->will($this->returnValue(array( 'file' => array( 'mapping' => 'dummy_file', @@ -240,17 +250,15 @@ public function testConfiguredNamerRetrievedFromContainer() ) ))); - $factory = new PropertyMappingFactory($this->container, $this->mapping, $this->adapter, $mappings); - $mappings = $factory->fromObject($obj); + $factory = new PropertyMappingFactory($this->container, $this->metadata, $this->adapter, $mappings); + $mappings = $factory->fromObject($this->object); $this->assertEquals(1, count($mappings)); - if (count($mappings) > 0) { - $mapping = $mappings[0]; + $mapping = $mappings[0]; - $this->assertEquals($namer, $mapping->getNamer()); - $this->assertTrue($mapping->hasNamer()); - } + $this->assertEquals($namer, $mapping->getNamer()); + $this->assertTrue($mapping->hasNamer()); } /** @@ -261,20 +269,20 @@ public function testConfiguredNamerRetrievedFromContainer() protected function getContainerMock() { return $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); } /** - * Creates a mock mapping reader. + * Creates a mock metadata reader. * - * @return \Vich\UploaderBundle\Mapping\MappingReader The mapping reader. + * @return \Vich\UploaderBundle\Metadata\MetadataReader The metadata reader. */ - protected function getMappingMock() + protected function getMetadataReaderMock() { - return $this->getMockBuilder('Vich\UploaderBundle\Mapping\MappingReader') - ->disableOriginalConstructor() - ->getMock(); + return $this->getMockBuilder('Vich\UploaderBundle\Metadata\MetadataReader') + ->disableOriginalConstructor() + ->getMock(); } /** @@ -285,7 +293,7 @@ protected function getMappingMock() protected function getAdapterMock() { return $this->getMockBuilder('Vich\UploaderBundle\Adapter\AdapterInterface') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); } } diff --git a/Tests/Mapping/ProperyMappingTest.php b/Tests/Mapping/PropertyMappingTest.php similarity index 64% rename from Tests/Mapping/ProperyMappingTest.php rename to Tests/Mapping/PropertyMappingTest.php index a9ad0e60..fc37e344 100644 --- a/Tests/Mapping/ProperyMappingTest.php +++ b/Tests/Mapping/PropertyMappingTest.php @@ -17,15 +17,15 @@ class PropertyMappingTest extends \PHPUnit_Framework_TestCase */ public function testConfiguredMappingAccess() { - $prop = new PropertyMapping(); + $prop = new PropertyMapping('file', 'fileName'); $prop->setMapping(array( - 'delete_on_remove' => true, - 'delete_on_update' => true, - 'upload_destination' => '/tmp', - 'inject_on_load' => true + 'delete_on_remove' => true, + 'delete_on_update' => true, + 'upload_destination' => '/tmp', + 'inject_on_load' => true )); - $this->assertEquals($prop->getUploadDir(), '/tmp'); + $this->assertEquals('/tmp', $prop->getUploadDestination()); $this->assertTrue($prop->getDeleteOnRemove()); $this->assertTrue($prop->getInjectOnLoad()); } diff --git a/Tests/Metadata/Driver/YamlTest.php b/Tests/Metadata/Driver/YamlTest.php index 5eb08f76..0986506c 100644 --- a/Tests/Metadata/Driver/YamlTest.php +++ b/Tests/Metadata/Driver/YamlTest.php @@ -20,7 +20,7 @@ public function testInconsistentYamlFile() $rClass = new \ReflectionClass('\DateTime'); $driver = $this->getDriver($rClass); - $driver->mapping_content = array(); + $driver->mappingContent = array(); $driver->loadMetadataForClass($rClass); } @@ -33,7 +33,7 @@ public function testLoadMetadataForClass($mapping, $expectedMetadata) $rClass = new \ReflectionClass('\DateTime'); $driver = $this->getDriver($rClass); - $driver->mapping_content = array( + $driver->mappingContent = array( $rClass->name => $mapping ); @@ -111,10 +111,10 @@ public function fieldsProvider() class TestableYaml extends Yaml { - public $mapping_content; + public $mappingContent; protected function loadMappingFile($file) { - return $this->mapping_content; + return $this->mappingContent; } } diff --git a/Tests/Naming/OrignameNamerTest.php b/Tests/Naming/OrignameNamerTest.php index e2b2da01..ba0abc02 100644 --- a/Tests/Naming/OrignameNamerTest.php +++ b/Tests/Naming/OrignameNamerTest.php @@ -1,10 +1,9 @@ setFile($file); + $mapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') + ->disableOriginalConstructor() + ->getMock(); + + $mapping + ->expects($this->once()) + ->method('getFile') + ->with($entity) + ->will($this->returnValue($file)); + $namer = new OrignameNamer(); - $this->assertRegExp($pattern, $namer->name($entity, 'file')); + $this->assertRegExp($pattern, $namer->name($mapping, $entity)); } } diff --git a/Tests/Naming/UniqidNamerTest.php b/Tests/Naming/UniqidNamerTest.php index 63d7e07f..cd85a98b 100644 --- a/Tests/Naming/UniqidNamerTest.php +++ b/Tests/Naming/UniqidNamerTest.php @@ -1,6 +1,6 @@ setFile($file); + $mapping = $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') + ->disableOriginalConstructor() + ->getMock(); + + $mapping + ->expects($this->once()) + ->method('getFile') + ->with($entity) + ->will($this->returnValue($file)); + $namer = new UniqidNamer(); - $this->assertRegExp($pattern, $namer->name($entity, 'file')); + $this->assertRegExp($pattern, $namer->name($mapping, $entity)); } } diff --git a/Tests/Storage/FileSystemStorageTest.php b/Tests/Storage/FileSystemStorageTest.php index be7a94d6..e4b549e7 100644 --- a/Tests/Storage/FileSystemStorageTest.php +++ b/Tests/Storage/FileSystemStorageTest.php @@ -2,6 +2,8 @@ namespace Vich\UploaderBundle\Tests\Storage; +use org\bovigo\vfs\vfsStream; + use Vich\UploaderBundle\Storage\FileSystemStorage; use Vich\UploaderBundle\Tests\DummyEntity; @@ -18,512 +20,176 @@ class FileSystemStorageTest extends \PHPUnit_Framework_TestCase protected $factory; /** - * Sets up the test. + * @var PropertyMapping */ - public function setUp() - { - $this->factory = $this->getFactoryMock(); - } + protected $mapping; /** - * Tests the upload method skips a mapping which has a null - * uploadable property value. + * @var DummyEntity */ - public function testUploadSkipsMappingOnNullFile() - { - $obj = new DummyEntity(); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $mapping - ->expects($this->once()) - ->method('getPropertyValue') - ->will($this->returnValue(null)); - - $mapping - ->expects($this->never()) - ->method('hasNamer'); - - $mapping - ->expects($this->never()) - ->method('getNamer'); - - $mapping - ->expects($this->never()) - ->method('getFileNameProperty'); - - $this->factory - ->expects($this->once()) - ->method('fromObject') - ->with($obj) - ->will($this->returnValue(array($mapping))); - - $storage = new FileSystemStorage($this->factory); - $storage->upload($obj); - } + protected $object; /** - * Tests the upload method skips a mapping which has an uploadable - * field property value that is not an instance of UploadedFile. + * @var FileSystemStorage */ - public function testUploadSkipsMappingOnNonUploadedFileInstance() - { - $obj = new DummyEntity(); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\File') - ->disableOriginalConstructor() - ->getMock(); - - $mapping - ->expects($this->once()) - ->method('getPropertyValue') - ->will($this->returnValue($file)); - - $mapping - ->expects($this->never()) - ->method('hasNamer'); - - $mapping - ->expects($this->never()) - ->method('getNamer'); - - $mapping - ->expects($this->never()) - ->method('getFileNameProperty'); - - $this->factory - ->expects($this->once()) - ->method('fromObject') - ->with($obj) - ->will($this->returnValue(array($mapping))); - - $storage = new FileSystemStorage($this->factory); - $storage->upload($obj); - } + protected $storage; /** - * Test the remove method does not remove a file that is configured - * to not be deleted upon removal of the entity. + * @var vfsStreamDirectory */ - public function testRemoveSkipsConfiguredNotToDeleteOnRemove() - { - $obj = new DummyEntity(); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $mapping - ->expects($this->once()) - ->method('getDeleteOnRemove') - ->will($this->returnValue(false)); - - $mapping - ->expects($this->never()) - ->method('getFileNameProperty'); - - $this->factory - ->expects($this->once()) - ->method('fromObject') - ->with($obj) - ->will($this->returnValue(array($mapping))); - - $storage = new FileSystemStorage($this->factory); - $storage->remove($obj); - } + protected $root; /** - * Test the remove method skips trying to remove a file whose file name - * property value returns null. + * Sets up the test. */ - public function testRemoveSkipsNullFileNameProperty() + public function setUp() { - $obj = new DummyEntity(); - - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue(null)); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - $mapping - ->expects($this->once()) - ->method('getDeleteOnRemove') - ->will($this->returnValue(true)); - - $mapping - ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); + $this->factory = $this->getFactoryMock(); + $this->mapping = $this->getMappingMock(); + $this->object = new DummyEntity(); + $this->storage = new FileSystemStorage($this->factory); - $mapping - ->expects($this->never()) - ->method('getUploadDir'); + $this->root = vfsStream::setup('vich_uploader_bundle', null, array( + 'uploads' => array( + 'test.txt' => 'some content' + ), + )); $this->factory - ->expects($this->once()) - ->method('fromObject') - ->with($obj) - ->will($this->returnValue(array($mapping))); - - $storage = new FileSystemStorage($this->factory); - $storage->remove($obj); - } - /** - * Test the remove method skips trying to remove a file that no longer exists - */ - public function testRemoveSkipsNonExistingFile() - { - $obj = new DummyEntity(); - - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); - - $propertyMock = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - - $propertyMock - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue(null)); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - $mapping - ->expects($this->once()) - ->method('getDeleteOnRemove') - ->will($this->returnValue(true)); - - $mapping - ->expects($this->once()) - ->method('getUploadDir') - ->will($this->returnValue('/tmp')); - - $mapping ->expects($this->any()) - ->method('getProperty') - ->will($this->returnValue($propertyMock)); - - $mapping - ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - - $this->factory - ->expects($this->once()) - ->method('fromObject') - ->with($obj) - ->will($this->returnValue(array($mapping))); - - $storage = new FileSystemStorage($this->factory); - try { - $storage->remove($obj); - } catch (\Exception $e) { - $this->fail('We tried to remove nonexistent file'); - } + ->method('fromObject') + ->with($this->object) + ->will($this->returnValue(array($this->mapping))); } /** - * Test the resolve path method. + * @dataProvider invalidFileProvider + * @group upload */ - public function testResolvePath() + public function testUploadSkipsMappingOnInvalidFile() { - $obj = new DummyEntity(); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); - - $mapping - ->expects($this->once()) - ->method('getUploadDir') - ->will($this->returnValue('/tmp')); - - $mapping - ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - - $this->factory - ->expects($this->once()) - ->method('fromField') - ->with($obj, 'file') - ->will($this->returnValue($mapping)); - - $storage = new FileSystemStorage($this->factory); - $path = $storage->resolvePath($obj, 'file'); - - $this->assertEquals(sprintf('/tmp%sfile.txt', DIRECTORY_SEPARATOR), $path); + $this->mapping + ->expects($this->once()) + ->method('getFile') + ->will($this->returnValue(null)); + $this->mapping + ->expects($this->never()) + ->method('hasNamer'); + $this->mapping + ->expects($this->never()) + ->method('getNamer'); + $this->mapping + ->expects($this->never()) + ->method('getFileName'); + + $this->storage->upload($this->object); } - /** - * Test the resolve uri - * - * @dataProvider resolveUriDataProvider - */ - public function testResolveUri($uploadDir, $uri) + public function invalidFileProvider() { - $obj = new DummyEntity(); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); - - $mapping - ->expects($this->once()) - ->method('getUploadDir') - ->will($this->returnValue($uploadDir)); - - $mapping - ->expects($this->once()) - ->method('getUriPrefix') - ->will($this->returnValue('/uploads')); - - $mapping - ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - - $this->factory - ->expects($this->once()) - ->method('fromField') - ->with($obj, 'file') - ->will($this->returnValue($mapping)); - - $storage = new FileSystemStorage($this->factory); - $path = $storage->resolveUri($obj, 'file'); - - $this->assertEquals($uri, $path); - } + $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\File') + ->disableOriginalConstructor() + ->getMock(); - public function resolveUriDataProvider() - { return array( - array( - '/abs/path/web/uploads', - '/uploads/file.txt' - ), - array( - 'c:\abs\path\web\uploads', - '/uploads/file.txt' - ), - array( - '/abs/path/web/project/web/uploads', - '/uploads/file.txt' - ), - array( - '/abs/path/web/project/web/uploads/custom/dir', - '/uploads/custom/dir/file.txt' - ), + // skipped because null + array( null ), + // skipped because not even a file + array( new \DateTime() ), + // skipped because not instance of UploadedFile + array( $file ), ); } /** - * Test the resolve path method throws exception - * when an invaid field name is specified. + * Test that file upload moves uploaded file to correct directory and with + * correct filename. * - * @expectedException \InvalidArgumentException - */ - public function testResolvePathThrowsExceptionOnInvalidFieldName() - { - $obj = new DummyEntity(); - - $this->factory - ->expects($this->once()) - ->method('fromField') - ->with($obj, 'oops') - ->will($this->returnValue(null)); - - $storage = new FileSystemStorage($this->factory); - $storage->resolvePath($obj, 'oops'); - } - - /** - * Test that file upload moves uploaded file to correct directory and with correct filename + * @group upload */ public function testUploadedFileIsCorrectlyMoved() { - $obj = new DummyEntity(); - $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') ->disableOriginalConstructor() ->getMock(); - $file - ->expects($this->any()) + ->expects($this->once()) ->method('getClientOriginalName') ->will($this->returnValue('filename.txt')); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - - $name = new \stdClass(); - - $prop - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue($name)); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $mapping - ->expects($this->any()) - ->method('getProperty') - ->will($this->returnValue($prop)); - - $mapping + $this->mapping ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - - $mapping - ->expects($this->once()) - ->method('getPropertyValue') - ->with($obj) + ->method('getFile') + ->with($this->object) ->will($this->returnValue($file)); - $mapping - ->expects($this->once()) - ->method('getDeleteOnUpdate') - ->will($this->returnValue(false)); - - $mapping - ->expects($this->once()) - ->method('hasNamer') - ->will($this->returnValue(false)); - - $mapping + $this->mapping ->expects($this->once()) - ->method('getUploadDir') - ->with($obj,$name) + ->method('getUploadDestination') ->will($this->returnValue('/dir')); - $this->factory - ->expects($this->once()) - ->method('fromObject') - ->with($obj) - ->will($this->returnValue(array($mapping))); - $file ->expects($this->once()) ->method('move') ->with('/dir', 'filename.txt'); - $storage = new FileSystemStorage($this->factory); - $storage->upload($obj); + $this->storage->upload($this->object); } /** - * Test file upload when filename contains directories - * @dataProvider filenameWithDirectoriesDataProvider + * Test file upload when filename contains directories + * + * @dataProvider filenameWithDirectoriesDataProvider + * @group upload */ public function testFilenameWithDirectoriesIsUploadedToCorrectDirectory($dir, $filename, $expectedDir, $expectedFileName) { - $obj = new DummyEntity(); - $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') ->disableOriginalConstructor() ->getMock(); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - - $name = new \stdClass(); - $namer = $this->getMockBuilder('Vich\UploaderBundle\Naming\NamerInterface') ->disableOriginalConstructor() ->getMock(); - $namer ->expects($this->once()) ->method('name') - ->with($obj, $name) + ->with($this->mapping, $this->object) ->will($this->returnValue($filename)); - $prop - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue($name)); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $mapping + $this->mapping ->expects($this->once()) ->method('getNamer') ->will($this->returnValue($namer)); - $mapping - ->expects($this->any()) - ->method('getProperty') - ->will($this->returnValue($prop)); - - $mapping + $this->mapping ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - - $mapping - ->expects($this->once()) - ->method('getPropertyValue') - ->with($obj) + ->method('getFile') + ->with($this->object) ->will($this->returnValue($file)); - $mapping + $this->mapping ->expects($this->once()) ->method('getDeleteOnUpdate') ->will($this->returnValue(false)); - $mapping + $this->mapping ->expects($this->once()) ->method('hasNamer') ->will($this->returnValue(true)); - $mapping + $this->mapping ->expects($this->once()) - ->method('getUploadDir') - ->with($obj,$name) + ->method('getUploadDestination') ->will($this->returnValue($dir)); - $this->factory - ->expects($this->once()) - ->method('fromObject') - ->with($obj) - ->will($this->returnValue(array($mapping))); - $file ->expects($this->once()) ->method('move') ->with($expectedDir, $expectedFileName); - $storage = new FileSystemStorage($this->factory); - $storage->upload($obj); + $this->storage->upload($this->object); } @@ -546,15 +212,188 @@ public function filenameWithDirectoriesDataProvider() } /** - * Creates a mock factory. + * @dataProvider removeDataProvider + * @group remove + */ + public function testRemove($deleteOnRemove, $uploadDir, $fileName) + { + $this->mapping + ->expects($this->once()) + ->method('getDeleteOnRemove') + ->will($this->returnValue($deleteOnRemove)); + + // if the file should be deleted, we'll need its name + if ($deleteOnRemove) { + $this->mapping + ->expects($this->once()) + ->method('getFileName') + ->will($this->returnValue($fileName)); + } else { + $this->mapping + ->expects($this->never()) + ->method('getFileName'); + } + + // if the file should be deleted and we have its name, then we need the + // upload dir + if ($deleteOnRemove && $fileName !== null) { + $this->mapping + ->expects($this->once()) + ->method('getUploadDestination') + ->will($this->returnValue($this->root->url() . DIRECTORY_SEPARATOR . $uploadDir)); + } else { + $this->mapping + ->expects($this->never()) + ->method('getUploadDestination'); + } + + $this->storage->remove($this->object); + + // the file should have been deleted + if ($deleteOnRemove && $fileName !== null) { + $this->assertFalse($this->root->hasChild($uploadDir . DIRECTORY_SEPARATOR . $fileName)); + } + } + + public function removeDataProvider() + { + return array( + // deleteOnRemove uploadDir fileName + // don't configured to be deleted upon removal of the entity + array(false, null, null), + // configured, but not file present + array(true, null, null), + // configured and present in the filesystem + array(true, '/uploads', 'test.txt'), + // configured, but file already deleted + array(true, '/uploads', 'file.txt'), + ); + } + + /** + * Test the resolve path method. + * + * @group resolvePath + */ + public function testResolvePath() + { + $this->mapping + ->expects($this->once()) + ->method('getUploadDestination') + ->will($this->returnValue('/tmp')); + $this->mapping + ->expects($this->once()) + ->method('getFileName') + ->will($this->returnValue('file.txt')); + + $this->factory + ->expects($this->once()) + ->method('fromField') + ->with($this->object, 'file') + ->will($this->returnValue($this->mapping)); + + $path = $this->storage->resolvePath($this->object, 'file'); + + $this->assertEquals(sprintf('/tmp%sfile.txt', DIRECTORY_SEPARATOR), $path); + } + + /** + * Test the resolve path method throws exception + * when an invaid field name is specified. + * + * @expectedException InvalidArgumentException + * @group resolvePath + */ + public function testResolvePathThrowsExceptionOnInvalidFieldName() + { + $this->factory + ->expects($this->once()) + ->method('fromField') + ->with($this->object, 'oops') + ->will($this->returnValue(null)); + + $this->storage->resolvePath($this->object, 'oops'); + } + + /** + * Test the resolve uri + * + * @dataProvider resolveUriDataProvider + * @group resolveUri + */ + public function testResolveUri($uploadDir, $file, $uri) + { + $this->mapping + ->expects($this->once()) + ->method('getUploadDestination') + ->will($this->returnValue($uploadDir)); + + $this->mapping + ->expects($this->once()) + ->method('getUriPrefix') + ->will($this->returnValue('/uploads')); + + $this->mapping + ->expects($this->once()) + ->method('getFileName') + ->will($this->returnValue($file)); + + $this->factory + ->expects($this->once()) + ->method('fromField') + ->with($this->object, 'file') + ->will($this->returnValue($this->mapping)); + + $this->assertEquals($uri, $this->storage->resolveUri($this->object, 'file')); + } + + public function resolveUriDataProvider() + { + return array( + array( + '/abs/path/web/uploads', + 'file.txt', + '/uploads/file.txt' + ), + array( + 'c:\abs\path\web\uploads', + 'file.txt', + '/uploads/file.txt' + ), + array( + '/abs/path/web/project/web/uploads', + 'file.txt', + '/uploads/file.txt' + ), + array( + '/abs/path/web/project/web/uploads/foo', + 'custom/dir/file.txt', + '/uploads/foo/custom/dir/file.txt' + ), + array( + '/abs/path/web/project/web/uploads/custom/dir', + 'file.txt', + '/uploads/custom/dir/file.txt' + ), + ); + } + + /** + * Creates a mock mapping-factory. * * @return \Vich\UploaderBundle\Mapping\PropertyMappingFactory The factory. */ protected function getFactoryMock() { return $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMappingFactory') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); } + protected function getMappingMock() + { + return $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') + ->disableOriginalConstructor() + ->getMock(); + } } diff --git a/Tests/Storage/GaufretteStorageTest.php b/Tests/Storage/GaufretteStorageTest.php index 99602aaa..16d417da 100644 --- a/Tests/Storage/GaufretteStorageTest.php +++ b/Tests/Storage/GaufretteStorageTest.php @@ -40,11 +40,10 @@ public function testUploadSkipsMappingOnNullFile() { $obj = new DummyEntity(); - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - + $mapping = $this->getMappingMock(); $mapping ->expects($this->once()) - ->method('getPropertyValue') + ->method('getFile') ->will($this->returnValue(null)); $mapping @@ -55,10 +54,6 @@ public function testUploadSkipsMappingOnNullFile() ->expects($this->never()) ->method('getNamer'); - $mapping - ->expects($this->never()) - ->method('getFileNameProperty'); - $this->factory ->expects($this->once()) ->method('fromObject') @@ -77,7 +72,7 @@ public function testUploadSkipsMappingOnNonUploadedFileInstance() { $obj = new DummyEntity(); - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); + $mapping = $this->getMappingMock(); $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\File') ->disableOriginalConstructor() @@ -85,7 +80,7 @@ public function testUploadSkipsMappingOnNonUploadedFileInstance() $mapping ->expects($this->once()) - ->method('getPropertyValue') + ->method('getFile') ->will($this->returnValue($file)); $mapping @@ -96,10 +91,6 @@ public function testUploadSkipsMappingOnNonUploadedFileInstance() ->expects($this->never()) ->method('getNamer'); - $mapping - ->expects($this->never()) - ->method('getFileNameProperty'); - $this->factory ->expects($this->once()) ->method('fromObject') @@ -118,16 +109,14 @@ public function testRemoveSkipsConfiguredNotToDeleteOnRemove() { $obj = new DummyEntity(); - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - + $mapping = $this->getMappingMock(); $mapping ->expects($this->once()) ->method('getDeleteOnRemove') ->will($this->returnValue(false)); - $mapping ->expects($this->never()) - ->method('getFileNameProperty'); + ->method('getFileName'); $this->factory ->expects($this->once()) @@ -147,16 +136,7 @@ public function testRemoveSkipsNullFileNameProperty() { $obj = new DummyEntity(); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue(null)); - - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); + $mapping = $this->getMappingMock(); $mapping ->expects($this->once()) ->method('getDeleteOnRemove') @@ -164,12 +144,12 @@ public function testRemoveSkipsNullFileNameProperty() $mapping ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); + ->method('getFileName') + ->will($this->returnValue(null)); $mapping ->expects($this->never()) - ->method('getUploadDir'); + ->method('getUploadDestination'); $this->factory ->expects($this->once()) @@ -188,26 +168,17 @@ public function testResolvePath() { $obj = new DummyEntity(); - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); + $mapping = $this->getMappingMock(); $mapping ->expects($this->once()) - ->method('getUploadDir') + ->method('getUploadDestination') ->will($this->returnValue('filesystemKey')); $mapping ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); + ->method('getFileName') + ->will($this->returnValue('file.txt')); $this->factory ->expects($this->once()) @@ -228,26 +199,17 @@ public function testResolvePathWithChangedProtocol() { $obj = new DummyEntity(); - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); - - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->once()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); + $mapping = $this->getMappingMock(); $mapping ->expects($this->once()) - ->method('getUploadDir') + ->method('getUploadDestination') ->will($this->returnValue('filesystemKey')); $mapping ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); + ->method('getFileName') + ->will($this->returnValue('file.txt')); $this->factory ->expects($this->once()) @@ -266,38 +228,21 @@ public function testResolvePathWithChangedProtocol() */ public function testThatRemoveMethodDoesDeleteFile() { - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); + $mapping = $this->getMappingMock(); $obj = new DummyEntity(); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->any()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); - $prop - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue('nameProperty')); - $mapping ->expects($this->once()) ->method('getDeleteOnRemove') ->will($this->returnValue(true)); $mapping ->expects($this->any()) - ->method('getUploadDir') + ->method('getUploadDestination') ->will($this->returnValue('filesystemKey')); $mapping ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - $mapping - ->expects($this->once()) - ->method('getProperty') - ->will($this->returnValue($prop)); + ->method('getFileName') + ->will($this->returnValue('file.txt')); $filesystem = $this->getFilesystemMock(); $filesystem @@ -320,7 +265,7 @@ public function testThatRemoveMethodDoesDeleteFile() ->will($this->returnValue(array($mapping))); $storage = new GaufretteStorage($this->factory, $this->filesystemMap); - $storage->remove($obj, 'file'); + $storage->remove($obj); } /** @@ -328,38 +273,21 @@ public function testThatRemoveMethodDoesDeleteFile() */ public function testRemoveNotFoundFile() { - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); + $mapping = $this->getMappingMock(); $obj = new DummyEntity(); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - $prop - ->expects($this->any()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); - $prop - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue('nameProperty')); - $mapping ->expects($this->once()) ->method('getDeleteOnRemove') ->will($this->returnValue(true)); $mapping ->expects($this->any()) - ->method('getUploadDir') + ->method('getUploadDestination') ->will($this->returnValue('filesystemKey')); $mapping ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - $mapping - ->expects($this->once()) - ->method('getProperty') - ->will($this->returnValue($prop)); + ->method('getFileName') + ->will($this->returnValue('file.txt')); $filesystem = $this->getFilesystemMock(); $filesystem @@ -383,7 +311,7 @@ public function testRemoveNotFoundFile() ->will($this->returnValue(array($mapping))); $storage = new GaufretteStorage($this->factory, $this->filesystemMap); - $storage->remove($obj, 'file'); + $storage->remove($obj); } /** @@ -410,37 +338,20 @@ public function testUploadSetsMetadataWhenUsingMetadataSupporterAdapter() { $obj = new DummyEntity(); - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); + $mapping = $this->getMappingMock(); $adapter = $this->getMockBuilder('\Gaufrette\Adapter\MetadataSupporter') ->disableOriginalConstructor() ->setMethods(array('setMetadata', 'getMetadata')) ->getMock(); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - - $prop - ->expects($this->any()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); - - $prop - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue('nameProperty')); - $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') ->disableOriginalConstructor() ->getMock(); - $file ->expects($this->once()) ->method('getClientOriginalName') ->will($this->returnValue('filename')); - $file ->expects($this->once()) ->method('getPathname') @@ -448,24 +359,14 @@ public function testUploadSetsMetadataWhenUsingMetadataSupporterAdapter() $mapping ->expects($this->once()) - ->method('getPropertyValue') + ->method('getFile') ->will($this->returnValue($file)); $mapping ->expects($this->once()) - ->method('getUploadDir') + ->method('getUploadDestination') ->will($this->returnValue('filesystemKey')); - $mapping - ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - - $mapping - ->expects($this->once()) - ->method('getProperty') - ->will($this->returnValue($prop)); - $filesystem = $this ->getMockBuilder('\Gaufrette\Filesystem') ->disableOriginalConstructor() @@ -534,36 +435,19 @@ public function testUploadDoesNotSetMetadataWhenUsingNonMetadataSupporterAdapter { $obj = new DummyEntity(); - $mapping = $this->getMock('Vich\UploaderBundle\Mapping\PropertyMapping'); + $mapping = $this->getMappingMock(); $adapter = $this->getMockBuilder('\Gaufrette\Adapter\Apc') ->disableOriginalConstructor() ->getMock(); - $prop = $this->getMockBuilder('\ReflectionProperty') - ->disableOriginalConstructor() - ->getMock(); - - $prop - ->expects($this->any()) - ->method('getValue') - ->with($obj) - ->will($this->returnValue('file.txt')); - - $prop - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue('nameProperty')); - $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') ->disableOriginalConstructor() ->getMock(); - $file ->expects($this->once()) ->method('getClientOriginalName') ->will($this->returnValue('filename')); - $file ->expects($this->once()) ->method('getPathname') @@ -571,24 +455,14 @@ public function testUploadDoesNotSetMetadataWhenUsingNonMetadataSupporterAdapter $mapping ->expects($this->once()) - ->method('getPropertyValue') + ->method('getFile') ->will($this->returnValue($file)); $mapping ->expects($this->once()) - ->method('getUploadDir') + ->method('getUploadDestination') ->will($this->returnValue('filesystemKey')); - $mapping - ->expects($this->once()) - ->method('getFileNameProperty') - ->will($this->returnValue($prop)); - - $mapping - ->expects($this->once()) - ->method('getProperty') - ->will($this->returnValue($prop)); - $filesystem = $this ->getMockBuilder('\Gaufrette\Filesystem') ->disableOriginalConstructor() @@ -691,4 +565,11 @@ protected function getFilesystemMock() ->disableOriginalConstructor() ->getMock(); } + + protected function getMappingMock() + { + return $this->getMockBuilder('Vich\UploaderBundle\Mapping\PropertyMapping') + ->disableOriginalConstructor() + ->getMock(); + } } diff --git a/Twig/Extension/UploaderExtension.php b/Twig/Extension/UploaderExtension.php index f711406f..4414667a 100644 --- a/Twig/Extension/UploaderExtension.php +++ b/Twig/Extension/UploaderExtension.php @@ -59,12 +59,14 @@ public function getFunctions() * Gets the public path for the file associated with the uploadable * object. * - * @param object $obj The object. - * @param string $field The field. + * @param object $obj The object. + * @param string $field The field. + * @param string $className The object's class. Mandatory if $obj can't be used to determine it. + * * @return string The public path. */ - public function asset($obj, $field) + public function asset($obj, $field, $className = null) { - return $this->helper->asset($obj, $field); + return $this->helper->asset($obj, $field, $className); } } diff --git a/VichUploaderBundle.php b/VichUploaderBundle.php index 66dfd036..76642085 100644 --- a/VichUploaderBundle.php +++ b/VichUploaderBundle.php @@ -2,8 +2,11 @@ namespace Vich\UploaderBundle; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Vich\UploaderBundle\DependencyInjection\CompilerPass\RegisterPropelModelsPass; + /** * VichUploaderBundle. * @@ -11,4 +14,13 @@ */ class VichUploaderBundle extends Bundle { + /** + * {@inheritdoc} + */ + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->addCompilerPass(new RegisterPropelModelsPass()); + } } diff --git a/composer.json b/composer.json index 68a6925e..00c43a3f 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,17 @@ "php": ">=5.3.2", "symfony/framework-bundle": "~2.0", "jms/metadata": "~1.5", + "symfony/property-access": ">=2.2,<3.0", "symfony/finder": ">=2.0" }, "require-dev": { + "mikey179/vfsStream": "~1.0", "doctrine/orm": "@stable", "doctrine/mongodb-odm": "@dev", "knplabs/knp-gaufrette-bundle": "*", "phpunit/phpunit": "~3.7", + "propel/propel1": "~1", + "willdurand/propel-eventdispatcher-bundle": "~1.0", "symfony/yaml": "@stable" }, "suggest": { @@ -29,6 +33,7 @@ "doctrine/doctrine-bundle": "*", "doctrine/mongodb-odm-bundle": "*", "knplabs/knp-gaufrette-bundle": "*", + "willdurand/propel-eventdispatcher-bundle": ">=1.2", "symfony/yaml": "@stable" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f53c420f..00b2cfdf 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,16 @@ - + @@ -14,8 +24,9 @@ ./Resources ./Tests + ./vendor - +