├── .travis.composer.config.json ├── .travis.yml ├── Command └── RepairFileDataCommand.php ├── DataStorage ├── DataStorageInterface.php └── OrmDataStorage.php ├── DependencyInjection ├── Compiler │ └── FormPass.php ├── Configuration.php └── IphpFileStoreExtension.php ├── Driver └── AnnotationDriver.php ├── EventListener └── UploaderListener.php ├── File └── File.php ├── FileStorage ├── FileStorageInterface.php └── FileSystemStorage.php ├── Form ├── DataTransformer │ ├── FileDataTransformer.php │ └── FileDataViewTransformer.php └── Type │ ├── FileType.php │ └── FileTypeBindSubscriber.php ├── IphpFileStoreBundle.php ├── Mapping ├── Annotation │ ├── Uploadable.php │ └── UploadableField.php ├── PropertyMapping.php └── PropertyMappingFactory.php ├── Naming ├── DefaultDirectoryNamer.php ├── DefaultNamer.php └── NamerServiceInvoker.php ├── README.md ├── Resources ├── config │ ├── routing.xml │ └── services.xml ├── translations │ ├── messages.fr.xliff │ └── messages.ru.xliff └── views │ └── Form │ ├── fields-base.html.twig │ └── fields.html.twig ├── Tests ├── ChildOfDummyEntity.php ├── DataStorage │ └── Orm │ │ └── OrmDataStorageTest.php ├── Driver │ └── AnnotationDriverTest.php ├── DummyEntity.php ├── DummyEntityProxyORM.php ├── DummyEntitySeparateDataField.php ├── EventListener │ └── UploaderListenerTest.php ├── File │ └── FileTest.php ├── FileStorage │ ├── 123.jpg │ └── FileSystemStorageTest.php ├── Fixtures │ ├── files │ │ └── text.txt │ └── images │ │ ├── front-images-list.jpeg │ │ ├── github1.png │ │ ├── php-elephant.png │ │ └── sonata-admin-iphpfile.jpeg ├── Form │ ├── DataTransformer │ │ ├── FileDataTransformerTest.php │ │ └── FileDataViewTransformerTest.php │ └── Type │ │ └── FileTypeBindSubscriberTest.php ├── Functional │ ├── AppKernel.php │ ├── BaseTestCase.php │ ├── FileSaveTest.php │ ├── ImageEditTest.php │ ├── ImageUploadTest.php │ ├── TestBundle │ │ ├── Controller │ │ │ └── DefaultController.php │ │ ├── Entity │ │ │ └── Photo.php │ │ ├── Resources │ │ │ └── views │ │ │ │ └── Photo │ │ │ │ ├── edit.html.twig │ │ │ │ └── index.html.twig │ │ └── TestBundle.php │ ├── TestXmlConfigBundle │ │ ├── Entity │ │ │ ├── File.php │ │ │ └── UploadableEntity.php │ │ ├── Resources │ │ │ └── config │ │ │ │ └── doctrine │ │ │ │ └── File.orm.xml │ │ └── TestXmlConfigBundle.php │ └── config │ │ ├── database.php │ │ ├── default.yml │ │ ├── default_newfilepath.yml │ │ ├── doctrine.yml │ │ ├── framework.yml │ │ ├── routing.yml │ │ └── twig.yml ├── Mapping │ ├── Annotation │ │ └── UploadableFieldTest.php │ ├── PropertyMappingFactoryTest.php │ └── PropertyMappingTest.php ├── Mocks.php ├── Naming │ ├── DefaultDirectoryNamerTest.php │ ├── DefaultNamerTest.php │ └── NamerServiceInvokerTest.php ├── TwoFieldsDummyEntity.php └── bootstrap.php ├── composer.json └── phpunit.xml.dist /.travis.composer.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "config":{ 3 | "github-oauth":{ 4 | "github.com":"3bb968e28b023f978095519704e606d315121c95" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | 8 | env: 9 | - SYMFONY_VERSION=2.8.* 10 | - SYMFONY_VERSION=3.0.* 11 | - SYMFONY_VERSION=3.1.* 12 | 13 | addons: 14 | code_climate: 15 | repo_token: f0a01fd708e8f258d3532dd82d1b55c3873953ad56fa2796d178483b9c9d6228 16 | 17 | before_script: 18 | 19 | # Set the GitHub OAuth token to make use of the 6000 per hour rate limit 20 | - "mkdir -p ~/.composer" 21 | - cp .travis.composer.config.json ~/.composer/config.json 22 | - composer self-update 23 | - composer require symfony/asset:${SYMFONY_VERSION} --no-update 24 | - composer require symfony/intl:${SYMFONY_VERSION} --no-update 25 | - composer require symfony/event-dispatcher:${SYMFONY_VERSION} --no-update 26 | - composer require symfony/options-resolver:${SYMFONY_VERSION} --no-update 27 | - composer require symfony/form:${SYMFONY_VERSION} --no-update 28 | - composer require symfony/twig-bundle:${SYMFONY_VERSION} --no-update 29 | - composer require symfony/twig-bridge:${SYMFONY_VERSION} --no-update 30 | - composer require symfony/templating:${SYMFONY_VERSION} --no-update 31 | - composer require symfony/doctrine-bridge:${SYMFONY_VERSION} --no-update 32 | - composer require symfony/framework-bundle:${SYMFONY_VERSION} 33 | - composer install --dev 34 | 35 | notifications: 36 | email: vitiko@mail.ru 37 | -------------------------------------------------------------------------------- /Command/RepairFileDataCommand.php: -------------------------------------------------------------------------------- 1 | setName('iphp:filestore:repair') 21 | ->addOption('entity', null, InputOption::VALUE_REQUIRED, 'The entity class name (shortcut notation)') 22 | ->addOption('field', null, InputOption::VALUE_REQUIRED, 'The field with file data') 23 | ->addOption('maxresults', null, InputOption::VALUE_OPTIONAL, 'Max results limitation') 24 | ->addOption('webdir', null, InputOption::VALUE_OPTIONAL, 'Web dir for searching file') 25 | ->addOption('force', null, InputOption::VALUE_OPTIONAL, 'Force reupload and rename files'); 26 | 27 | } 28 | 29 | 30 | protected function getPropertyMappingFactory() 31 | { 32 | return $this->getContainer()->get('iphp.filestore.mapping.factory'); 33 | } 34 | 35 | 36 | protected function getWebDir(InputInterface $input) 37 | { 38 | $webDir = $input->getOption('webdir'); 39 | 40 | if (!$webDir && $this->getContainer()->has('iphp.web_dir')) 41 | $webDir = $this->getContainer()->getParameter('iphp.web_dir'); 42 | 43 | if (!$webDir) $webDir = str_replace('\\', '/', realpath($this->getContainer()->getParameter('kernel.root_dir') . '/../web/')); 44 | 45 | if (!$webDir) throw new \InvalidArgumentException (' 46 | For resolving IphpFileStoreBundle uploaded files need to set --webdir option'); 47 | 48 | return $webDir; 49 | } 50 | 51 | 52 | function getMaxResults(InputInterface $input) 53 | { 54 | $maxResults = $input->getOption('maxresults'); 55 | if (!$maxResults || !is_numeric($maxResults)) $maxResults = 1000; 56 | 57 | return $maxResults; 58 | } 59 | 60 | 61 | function getEntityManager() 62 | { 63 | return $this->getContainer()->get('doctrine')->getManager(); 64 | } 65 | 66 | function getMappingFromField($entity, $field) 67 | { 68 | return $this->getPropertyMappingFactory()->getMappingFromField($entity, new \ReflectionClass($entity), $field); 69 | } 70 | 71 | 72 | function getRepository($entityFullName) 73 | { 74 | return $this->getEntityManager()->getRepository($entityFullName); 75 | } 76 | 77 | function getEntityIds($entityFullName, $maxResults) 78 | { 79 | return $this->getEntityManager()->createQuery( 80 | "SELECT e.id FROM " . $entityFullName . " e ORDER BY e.id ASC" 81 | )->setMaxResults($maxResults)->getArrayResult(); 82 | } 83 | 84 | protected function execute(InputInterface $input, OutputInterface $output) 85 | { 86 | $entityFullName = $input->getOption('entity'); 87 | $field = $input->getOption('field'); 88 | $force = $input->getOption('force') ? true : false; 89 | $webDir = $this->getWebDir($input); 90 | 91 | 92 | foreach ($this->getEntityIds($entityFullName, $this->getMaxResults($input)) as $pos => $e) { 93 | 94 | $toFlush = false; 95 | $entity = $this->getRepository($entityFullName)->findOneById($e['id']); 96 | $fileData = $entity->{'get' . ucfirst($field)}(); 97 | 98 | if (!$fileData) continue; 99 | 100 | $fileNameByWebPath = $webDir . $fileData['path']; 101 | $fileNameByWebPathExists = file_exists($fileNameByWebPath); 102 | 103 | $resolvedFileName = $this->getMappingFromField($entity, $field)->resolveFileName($fileData['fileName']); 104 | $resolvedFileNameExists = file_exists($resolvedFileName) ? 'exists' : 'NO'; 105 | 106 | if (!$fileNameByWebPathExists && !$resolvedFileNameExists) { 107 | $output->writeln("can't find file "); 108 | continue; 109 | } 110 | 111 | if ($fileNameByWebPathExists && $resolvedFileNameExists && !$force) continue; 112 | 113 | //uploadedFile because need to move to new destination (not copy) 114 | $file = new UploadedFile ($fileNameByWebPathExists ? $fileNameByWebPath : $resolvedFileNameExists, 115 | $fileData['originalName'], $fileData['mimeType'], null, null, true); 116 | 117 | $entity->{'set' . ucfirst($field)} ($file); 118 | 119 | $this->getEntityManager()->persist($entity); 120 | 121 | $toFlush = true; 122 | if ($pos % 20 == 0 && $toFlush) $this->getEntityManager()->flush(); 123 | if ($pos % 100 == 0) $this->getEntityManager()->clear(); 124 | } 125 | 126 | $this->getEntityManager()->flush(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /DataStorage/DataStorageInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface DataStorageInterface 14 | { 15 | /** 16 | * Gets the mapped object from the event arguments. 17 | * 18 | * @param \Doctrine\Common\Persistence\Event\LifecycleEventArgs $e The event arguments. 19 | * @return object The mapped object. 20 | */ 21 | public function getObjectFromArgs(EventArgs $e); 22 | 23 | /** 24 | * Recomputes the change set for the object. 25 | * 26 | * @param \Doctrine\Common\Persistence\Event\LifecycleEventArgs $e The event arguments. 27 | */ 28 | public function recomputeChangeSet(EventArgs $e); 29 | 30 | /** 31 | * Gets the reflection class for the object taking 32 | * proxies into account. 33 | * 34 | * @param object $obj The object. 35 | * @return \ReflectionClass The reflection class. 36 | */ 37 | public function getReflectionClass($obj); 38 | 39 | 40 | public function postFlush ($obj, EventArgs $args); 41 | 42 | 43 | public function previusFieldDataIfChanged ($fieldName, EventArgs $args); 44 | } 45 | -------------------------------------------------------------------------------- /DataStorage/OrmDataStorage.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class OrmDataStorage implements DataStorageInterface 17 | { 18 | /** 19 | * {@inheritDoc} 20 | */ 21 | public function getObjectFromArgs(EventArgs $e) 22 | { 23 | return $e->getEntity(); 24 | } 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | public function recomputeChangeSet(EventArgs $e) 30 | { 31 | $obj = $this->getObjectFromArgs($e); 32 | 33 | /** 34 | * var \Doctrine\ORM\EntityManager 35 | */ 36 | $em = $e->getEntityManager(); 37 | 38 | $uow = $em->getUnitOfWork(); 39 | $metadata = $em->getClassMetadata(get_class($obj)); 40 | $uow->recomputeSingleEntityChangeSet($metadata, $obj); 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | public function getReflectionClass($obj) 47 | { 48 | if ($obj instanceof Proxy) { 49 | return new \ReflectionClass(get_parent_class($obj)); 50 | } 51 | 52 | return new \ReflectionClass($obj); 53 | } 54 | 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | public function postFlush($obj, EventArgs $args) 60 | { 61 | $args->getEntityManager()->persist($obj); 62 | $args->getEntityManager()->flush(); 63 | } 64 | 65 | 66 | public function previusFieldDataIfChanged($fieldName, EventArgs $args) 67 | { 68 | return $args->hasChangedField($fieldName) ? $args->getOldValue($fieldName) : null; 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/FormPass.php: -------------------------------------------------------------------------------- 1 | getParameter('twig.form.resources'); 18 | $resources[] = 'IphpFileStoreBundle:Form:fields.html.twig'; 19 | 20 | 21 | $container->setParameter('twig.form.resources', $resources); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Configuration implements ConfigurationInterface 14 | { 15 | /** 16 | * Gets the configuration tree builder for the extension. 17 | * 18 | * @return Tree The configuration tree builder 19 | */ 20 | public function getConfigTreeBuilder() 21 | { 22 | $tb = new TreeBuilder(); 23 | $root = $tb->root('iphp_file_store'); 24 | 25 | $root 26 | ->children() 27 | ->scalarNode('db_driver')->defaultValue('orm')->end() 28 | 29 | ->arrayNode('mappings') 30 | ->useAttributeAsKey('id') 31 | ->prototype('array') 32 | ->children() 33 | ->scalarNode('upload_dir')->end() 34 | ->scalarNode('upload_path')->end() 35 | ->scalarNode('delete_on_remove')->defaultTrue()->end() 36 | ->scalarNode('overwrite_duplicates')->defaultFalse()->end() 37 | 38 | 39 | ->arrayNode('namer') 40 | ->treatFalseLike(array ()) 41 | ->treatNullLike(array ('translit' => array('service' => 'iphp.filestore.namer.default'))) 42 | ->treatTrueLike(array ('translit' => array('service' => 'iphp.filestore.namer.default'))) 43 | ->useAttributeAsKey('id') 44 | ->prototype('array') 45 | 46 | ->children() 47 | 48 | ->scalarNode('service')->defaultValue('iphp.filestore.namer.default')->end() 49 | ->arrayNode('params') 50 | ->useAttributeAsKey('name') 51 | ->prototype('scalar')->end() 52 | ->end() 53 | ->end() 54 | ->end() 55 | ->end() 56 | 57 | 58 | 59 | ->arrayNode('directory_namer') 60 | 61 | ->useAttributeAsKey('id') 62 | ->prototype('array') 63 | 64 | ->children() 65 | 66 | ->scalarNode('service')->defaultValue('iphp.filestore.directory_namer.default')->end() 67 | ->arrayNode('params') 68 | ->useAttributeAsKey('name') 69 | ->prototype('scalar')->end() 70 | ->end() 71 | 72 | ->end() 73 | ->end() 74 | ->end() 75 | 76 | ->end() 77 | ->end() 78 | ->end() 79 | ->end() 80 | ; 81 | 82 | return $tb; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DependencyInjection/IphpFileStoreExtension.php: -------------------------------------------------------------------------------- 1 | 'doctrine.event_subscriber', 24 | // 'document' => '' 25 | ); 26 | 27 | /** 28 | * @var array $adapterMap 29 | */ 30 | protected $adapterMap = array( 31 | 'orm' => 'Iphp\FileStoreBundle\DataStorage\OrmDataStorage', 32 | //'document' => '' 33 | ); 34 | 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | public function load(array $configs, ContainerBuilder $container) 40 | { 41 | 42 | 43 | $configuration = new Configuration(); 44 | $config = $this->processConfiguration($configuration, $configs); 45 | 46 | 47 | 48 | $driver = strtolower($config['db_driver']); 49 | if (!in_array($driver, array_keys($this->tagMap))) { 50 | throw new \InvalidArgumentException( 51 | sprintf( 52 | 'Invalid "db_driver" configuration option specified: "%s"', 53 | $driver 54 | ) 55 | ); 56 | } 57 | 58 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); 59 | $loader->load('services.xml'); 60 | 61 | $mappings = isset($config['mappings']) ? $config['mappings'] : array(); 62 | 63 | $container->setParameter('iphp.filestore.mappings', $mappings); 64 | 65 | 66 | $container->setParameter('iphp.filestore.datastorage.class', $this->adapterMap[$driver]); 67 | 68 | //Add tag orm or mongodb to listener service 69 | $container->getDefinition('iphp.filestore.event_listener.uploader')->addTag($this->tagMap[$driver]); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Driver/AnnotationDriver.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class AnnotationDriver 13 | { 14 | /** 15 | * @var Reader $reader 16 | */ 17 | protected $reader; 18 | 19 | 20 | protected $uploadedClass = array(); 21 | 22 | protected $uploadedFields = array(); 23 | 24 | /** 25 | * Constructs a new instance of AnnotationDriver. 26 | * 27 | * @param \Doctrine\Common\Annotations\Reader $reader The annotation reader. 28 | */ 29 | public function __construct(Reader $reader) 30 | { 31 | $this->reader = $reader; 32 | } 33 | 34 | /** 35 | * Attempts to read the uploadable annotation. 36 | * 37 | * @param \ReflectionClass $class The reflection class. 38 | * @return null|\Iphp\FileStoreBundle\Annotation\Uploadable The annotation. 39 | */ 40 | public function readUploadable(\ReflectionClass $class) 41 | { 42 | $baseClassName = $className = $class->getNamespaceName() . '\\' . $class->getName(); 43 | do { 44 | if (isset($this->uploadedClass[$className])) { 45 | if ($baseClassName != $className) 46 | $this->uploadedClass[$baseClassName] = $this->uploadedClass[$className]; 47 | return $this->uploadedClass[$baseClassName]; 48 | } 49 | 50 | $annotation = $this->reader->getClassAnnotation($class, 'Iphp\FileStoreBundle\Mapping\Annotation\Uploadable'); 51 | if ($annotation) { 52 | $this->uploadedClass[$baseClassName] = $annotation; 53 | if ($baseClassName != $className) $this->uploadedClass[$className] = $annotation; 54 | 55 | return $annotation; 56 | } 57 | $class = $class->getParentClass(); 58 | if ($class) $className = $class->getNamespaceName() . '\\' . $class->getName(); 59 | } while ($class); 60 | 61 | return $annotation; 62 | } 63 | 64 | /** 65 | * Attempts to read the uploadable field annotations. 66 | * 67 | * @param \ReflectionClass $class The reflection class. 68 | * @return \Iphp\FileStoreBundle\Mapping\Annotation\UploadableField[] 69 | */ 70 | public function readUploadableFields(\ReflectionClass $class) 71 | { 72 | $propertyAnnotations = array(); 73 | 74 | foreach ($class->getProperties() as $prop) { 75 | 76 | $propertyAnnotation = $this->reader->getPropertyAnnotation($prop, 'Iphp\FileStoreBundle\Mapping\Annotation\UploadableField'); 77 | if (null !== $propertyAnnotation) { 78 | $propertyAnnotation->setFileUploadPropertyName($prop->getName()); 79 | $propertyAnnotations[] = $propertyAnnotation; 80 | } 81 | } 82 | 83 | return $propertyAnnotations; 84 | } 85 | 86 | /** 87 | * Attempts to read the uploadable field annotation of the 88 | * specified property. 89 | * 90 | * @param \ReflectionClass $class The class. 91 | * @param string $field The field 92 | * @return null|\Iphp\FileStoreBundle\Annotation\UploadableField The uploadable field. 93 | */ 94 | public function readUploadableField(\ReflectionClass $class, $field) 95 | { 96 | try { 97 | $prop = $class->getProperty($field); 98 | 99 | $field = $this->reader->getPropertyAnnotation($prop, 'Iphp\FileStoreBundle\Mapping\Annotation\UploadableField'); 100 | if (null === $field) { 101 | return null; 102 | } 103 | 104 | $field->setFileUploadPropertyName($prop->getName()); 105 | 106 | return $field; 107 | } catch (\ReflectionException $e) { 108 | return null; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /EventListener/UploaderListener.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class UploaderListener implements EventSubscriber 25 | { 26 | /** 27 | * Adapter for ORMor MongoDb 28 | * @var \Iphp\FileStoreBundle\DataStorage\DataStorageInterface $dataStorage 29 | */ 30 | protected $dataStorage; 31 | 32 | 33 | /** 34 | * @var \Iphp\FileStoreBundle\FileStorage\FileStorageInterface $fileStorage 35 | */ 36 | protected $fileStorage; 37 | 38 | 39 | /** 40 | * @var \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory $mappingFactory 41 | */ 42 | protected $mappingFactory; 43 | 44 | 45 | /** 46 | * @var \SplObjectStorage Temporary store for using in fileStorage 47 | */ 48 | protected $deferredFiles; 49 | 50 | 51 | /** 52 | * Constructs a new instance of UploaderListener. 53 | * 54 | * @param \Iphp\FileStoreBundle\DataStorage\DataStorageInterface $dataStorage The dataStorage 55 | * @param \Iphp\FileStoreBundle\FileStorage\FileStorageInterface $fileStorage The storage. 56 | * @param \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory $mappingFactory Mapping Factore 57 | */ 58 | public function __construct(DataStorageInterface $dataStorage, 59 | FileStorageInterface $fileStorage, 60 | PropertyMappingFactory $mappingFactory) 61 | { 62 | $this->dataStorage = $dataStorage; 63 | $this->fileStorage = $fileStorage; 64 | $this->mappingFactory = $mappingFactory; 65 | 66 | $this->deferredFiles = new \SplObjectStorage(); 67 | } 68 | 69 | 70 | public function hasDeferredObject($obj) 71 | { 72 | return isset($this->deferredFiles [$obj]) && $this->deferredFiles [$obj]; 73 | } 74 | 75 | public function hasDeferredPropertyMapping($obj, PropertyMapping $mapping) 76 | { 77 | return $this->hasDeferredObject($obj) && 78 | isset($this->deferredFiles [$obj][$mapping]) && $this->deferredFiles [$obj][$mapping]; 79 | } 80 | 81 | 82 | public function getDeferredObjectNum() 83 | { 84 | return count($this->deferredFiles); 85 | } 86 | 87 | 88 | /** 89 | * The events the listener is subscribed to. 90 | * 91 | * @return array The array of events. 92 | */ 93 | public function getSubscribedEvents() 94 | { 95 | return array( 96 | 'prePersist', 97 | 'postFlush', 98 | 'preUpdate', 99 | 'postRemove', 100 | ); 101 | } 102 | 103 | 104 | /** 105 | * @return \Iphp\FileStoreBundle\Mapping\PropertyMapping[] 106 | */ 107 | protected function getMappingsFromArgs(EventArgs $args) 108 | { 109 | $obj = $this->dataStorage->getObjectFromArgs($args); 110 | return $this->mappingFactory->getMappingsFromObject($obj, $this->dataStorage->getReflectionClass($obj)); 111 | } 112 | 113 | /** 114 | * Checks for for file to upload and store it for store at postFlush event 115 | * 116 | * @param \Doctrine\Common\EventArgs $args The event arguments. 117 | */ 118 | public function prePersist(EventArgs $args) 119 | { 120 | $obj = $this->dataStorage->getObjectFromArgs($args); 121 | $mappings = $this->mappingFactory->getMappingsFromObject($obj, $this->dataStorage->getReflectionClass($obj)); 122 | $curFiles = new \SplObjectStorage(); 123 | 124 | foreach ($mappings as $mapping) { 125 | $file = $mapping->getFileUploadPropertyValue(); 126 | if ($file instanceof File) $curFiles[$mapping] = $file; 127 | $mapping->setFileUploadPropertyValue(null); 128 | } 129 | 130 | //if ($curFiles) $this->deferredFiles [$mappings[0]->getObj()] = $curFiles; 131 | if (count($curFiles)) $this->deferredFiles [$obj] = $curFiles; 132 | } 133 | 134 | 135 | /** 136 | * Store at postFlush event because file namer mey need entity id, at prePersist event 137 | * system does not now auto generated entity id 138 | * @param \Doctrine\Common\EventArgs $args 139 | */ 140 | public function postFlush(EventArgs $args) 141 | { 142 | if (!$this->deferredFiles) return; 143 | 144 | foreach ($this->deferredFiles as $obj) { 145 | if (!$this->deferredFiles[$obj]) continue; 146 | 147 | foreach ($this->deferredFiles[$obj] as $mapping) { 148 | $fileData = $this->fileStorage->upload($mapping, $this->deferredFiles[$obj][$mapping]); 149 | $mapping->setFileDataPropertyValue($fileData); 150 | } 151 | 152 | unset($this->deferredFiles[$obj]); 153 | $this->dataStorage->postFlush($obj, $args); 154 | } 155 | } 156 | 157 | 158 | /** 159 | * Update the mapped file for Entity (obj) 160 | * 161 | * @param \Doctrine\Common\EventArgs $args 162 | */ 163 | public function preUpdate(\Doctrine\Common\EventArgs $args) 164 | { 165 | //All mappings from updated object 166 | $mappings = $this->getMappingsFromArgs($args); 167 | 168 | foreach ($mappings as $mapping) { 169 | if ($mapping->isUseOneProperty()) $this->updateUseOneProperties($args, $mapping); 170 | else $this->updateSeparateProperties($args, $mapping); 171 | } 172 | $this->dataStorage->recomputeChangeSet($args); 173 | } 174 | 175 | 176 | /** 177 | * upload field and file data field are NOT SAME ($obj->file and $obj->uploadFile) 178 | * @param EventArgs $args 179 | * @param PropertyMapping $mapping 180 | */ 181 | protected function updateUseOneProperties(\Doctrine\Common\EventArgs $args, PropertyMapping $mapping) 182 | { 183 | $uploadedFile = $mapping->getFileUploadPropertyValue(); 184 | 185 | //use getOldValue from ORM 186 | $currentFileData = $this->dataStorage->previusFieldDataIfChanged($mapping->getFileDataPropertyName(), $args); 187 | $currentFileName = $currentFileData ? $mapping->resolveFileName($currentFileData['fileName']) : null; 188 | 189 | 190 | //If no new file 191 | if (is_null($uploadedFile) || !($uploadedFile instanceof File)) { 192 | 193 | if ($currentFileData) { 194 | if (!$this->fileStorage->fileExists($currentFileName)) { 195 | 196 | $fileNameByWebDir = $_SERVER['DOCUMENT_ROOT'] . $currentFileData['path']; 197 | 198 | if ($this->fileStorage->fileExists($fileNameByWebDir)) { 199 | $uploadedFile = new UploadedFile ($fileNameByWebDir, 200 | $currentFileData['originalName'], $currentFileData['mimeType'], 201 | null, null, true); 202 | $fileData = $this->fileStorage->upload($mapping, $uploadedFile); 203 | $mapping->setFileDataPropertyValue($fileData); 204 | } 205 | 206 | } //Preserve old fileData if current file exist 207 | else $mapping->setFileDataPropertyValue($currentFileData); 208 | 209 | } 210 | 211 | 212 | } //set new File and uploaded file has deleted status - remove file 213 | else if ($uploadedFile instanceof \Iphp\FileStoreBundle\File\File && $uploadedFile->isDeleted()) { 214 | if ($this->fileStorage->removeFile($currentFileName)) $mapping->setFileDataPropertyValue(null); 215 | } //set new file - upload new file 216 | else { 217 | 218 | //Old value (file) exits and uploaded new file 219 | if ($currentFileData && !$this->fileStorage->isSameFile($uploadedFile, $currentFileName)) 220 | //before upload new file delete old file 221 | $this->fileStorage->removeFile($currentFileName); 222 | 223 | $fileData = $this->fileStorage->upload($mapping, $uploadedFile); 224 | $mapping->setFileDataPropertyValue($fileData); 225 | } 226 | 227 | } 228 | 229 | 230 | /** 231 | * upload field and file data field are SAME ($obj->file) 232 | * @param EventArgs $args 233 | * @param PropertyMapping $mapping 234 | */ 235 | protected function updateSeparateProperties(\Doctrine\Common\EventArgs $args, PropertyMapping $mapping) 236 | { 237 | $uploadedFile = $mapping->getFileUploadPropertyValue(); 238 | $currentFileData = $mapping->getFileDataPropertyValue(); 239 | $previousFileData = $this->dataStorage->previusFieldDataIfChanged($mapping->getFileDataPropertyName(), $args); 240 | 241 | 242 | $currentFileName = $previousFileData ? $mapping->resolveFileName($previousFileData['fileName']) : null; 243 | 244 | 245 | //delete current file 246 | if ($previousFileData && ( 247 | // $obj->setFile (null) 248 | is_null($currentFileData) || 249 | //$obj->setUploadFile (Iphp\File::createEmpty()->delete()) 250 | $uploadedFile && $uploadedFile instanceof \Iphp\FileStoreBundle\File\File && $uploadedFile->isDeleted()) 251 | ) { 252 | if ($this->fileStorage->removeFile($currentFileName)) $mapping->setFileDataPropertyValue(null); 253 | } //upload new file 254 | else if ($uploadedFile && $uploadedFile instanceof File) { 255 | 256 | //Old value (file) exists and uploaded new file 257 | if ($currentFileName && !$this->fileStorage->isSameFile($uploadedFile, $currentFileName)) 258 | //before upload new file delete old file 259 | $this->fileStorage->removeFile($currentFileName); 260 | 261 | $fileData = $this->fileStorage->upload($mapping, $uploadedFile); 262 | $mapping->setFileDataPropertyValue($fileData); 263 | } 264 | } 265 | 266 | 267 | /** 268 | * Removes the file if necessary. 269 | * 270 | * @param \Doctrine\Common\EventArgs $args The event arguments. 271 | */ 272 | public function postRemove(EventArgs $args) 273 | { 274 | $mappings = $this->getMappingsFromArgs($args); 275 | 276 | foreach ($mappings as $mapping) { 277 | if ($mapping->getDeleteOnRemove()) $this->fileStorage->removeFile($mapping->resolveFileName()); 278 | } 279 | 280 | } 281 | 282 | } 283 | -------------------------------------------------------------------------------- /File/File.php: -------------------------------------------------------------------------------- 1 | deleted = true; 34 | return $this; 35 | } 36 | 37 | public function isDeleted() 38 | { 39 | return $this->deleted ? true : false; 40 | } 41 | 42 | 43 | function isValid() 44 | { 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FileStorage/FileStorageInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface FileStorageInterface 14 | { 15 | 16 | 17 | /** 18 | * Uploads the files in the uploadable fields of the 19 | * specified object according to the property configuration. 20 | * 21 | * @param object $obj The object. 22 | */ 23 | public function upload(PropertyMapping $mapping, File $file); 24 | 25 | 26 | /** 27 | * @abstract 28 | * @param $fullFileName 29 | * @return boolean 30 | */ 31 | public function removeFile($fullFileName); 32 | 33 | 34 | /** 35 | * @abstract 36 | * @param $fullFileName 37 | * @return boolean 38 | */ 39 | public function fileExists($fullFileName); 40 | 41 | 42 | /** 43 | * @abstract 44 | * @param \Symfony\Component\HttpFoundation\File\File $file 45 | * @param $fullFileName 46 | * @return boolean 47 | */ 48 | public function isSameFile (File $file, $fullFileName); 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /FileStorage/FileSystemStorage.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FileSystemStorage implements FileStorageInterface 20 | { 21 | 22 | protected $webDir; 23 | 24 | protected $sameFileChecker; 25 | 26 | /** 27 | * Constructs a new instance of FileSystemStorage. 28 | * 29 | * @param 30 | */ 31 | public function __construct($webDir = null) 32 | { 33 | $this->webDir = $webDir; 34 | 35 | 36 | // @codeCoverageIgnoreStart 37 | $this->sameFileChecker = function (File $file, $fullFileName) 38 | { 39 | return $file->getRealPath() == realpath($fullFileName); 40 | }; 41 | // @codeCoverageIgnoreEnd 42 | } 43 | 44 | public function setWebDir($webDir ) 45 | { 46 | $this->webDir = $webDir; 47 | } 48 | 49 | public function getWebDir() 50 | { 51 | return $this->webDir; 52 | } 53 | 54 | public function setSameFileChecker (\Closure $checker) 55 | { 56 | $this->sameFileChecker = $checker; 57 | } 58 | 59 | 60 | 61 | 62 | protected function getOriginalName(File $file) 63 | { 64 | return $file instanceof UploadedFile ? 65 | $file->getClientOriginalName() : $file->getFilename(); 66 | } 67 | 68 | 69 | protected function getMimeType(File $file) 70 | { 71 | return $file instanceof UploadedFile ? 72 | $file->getClientMimeType() : $file->getMimeType(); 73 | } 74 | 75 | 76 | public function isSameFile (File $file, $fullFileName) 77 | { 78 | return call_user_func( 79 | $this->sameFileChecker, 80 | $file, 81 | $fullFileName); 82 | 83 | } 84 | 85 | 86 | protected function copyFile($source, $directory, $name) 87 | { 88 | $this->checkDirectory($directory); 89 | $target = $directory . DIRECTORY_SEPARATOR . basename($name); 90 | 91 | if (!@copy($source, $target)) { 92 | $error = error_get_last(); 93 | throw new FileException(sprintf('Could not copy the file "%s" to "%s" (%s)', $source, $target, strip_tags($error['message']))); 94 | } 95 | 96 | @chmod($target, 0666 & ~umask()); 97 | 98 | return new File($target); 99 | } 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | protected function checkDirectory ($directory) 108 | { 109 | if (!is_dir($directory)) { 110 | if (false === @mkdir($directory, 0777, true)) { 111 | 112 | // @codeCoverageIgnoreStart 113 | throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); 114 | // @codeCoverageIgnoreEnd 115 | } 116 | } elseif (!is_writable($directory)) { 117 | // @codeCoverageIgnoreStart 118 | throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); 119 | // @codeCoverageIgnoreEnd 120 | } 121 | 122 | return true; 123 | } 124 | 125 | 126 | /** 127 | * {@inheritDoc} 128 | * File may be \Symfony\Component\HttpFoundation\File\File or \Symfony\Component\HttpFoundation\File\UploadedFile 129 | */ 130 | public function upload(PropertyMapping $mapping, File $file) 131 | { 132 | $originalName = $this->getOriginalName($file); 133 | $mimeType = $this->getMimeType($file); 134 | 135 | //transform filename and directory name if namer exists in mapping definition 136 | list ($fileName, $webPath) = $mapping->prepareFileName($originalName, $this); 137 | $fullFileName = $mapping->resolveFileName($fileName); 138 | 139 | //check if file already placed in needed position 140 | if (!$this->isSameFile($file, $fullFileName)) { 141 | $fileInfo = pathinfo($fullFileName); 142 | 143 | if ($file instanceof UploadedFile) 144 | { 145 | $this->checkDirectory($fileInfo['dirname']); 146 | $file->move($fileInfo['dirname'], $fileInfo['basename']); 147 | } 148 | else $this->copyFile($file->getPathname(), $fileInfo['dirname'], $fileInfo['basename']); 149 | } 150 | 151 | 152 | $fileData = array( 153 | 'fileName' => $fileName, 154 | 'originalName' => $originalName, 155 | 'mimeType' => $mimeType, 156 | 'size' => filesize($fullFileName), 157 | 'path' => $webPath 158 | ); 159 | 160 | if (!$fileData['path']) 161 | $fileData['path'] = substr($fullFileName, strlen($this->webDir)); 162 | 163 | 164 | $ext = substr($originalName,strrpos ($originalName,'.')+1); 165 | 166 | if ((in_array($fileData['mimeType'], array('image/png', 'image/jpeg', 'image/pjpeg')) || 167 | in_array ($ext,array ('jpeg','jpg','png'))) 168 | && function_exists('getimagesize') 169 | ) { 170 | list($width, $height, $type) = @getimagesize($fullFileName); 171 | $fileData = array_merge($fileData, array( 172 | 'width' => $width, 'height' => $height 173 | )); 174 | } 175 | 176 | return $fileData; 177 | } 178 | 179 | 180 | /** 181 | * {@inheritDoc} 182 | */ 183 | public function removeFile($fullFileName) 184 | { 185 | 186 | if ($fullFileName && file_exists($fullFileName)) { 187 | @unlink($fullFileName); 188 | return !file_exists($fullFileName); 189 | } 190 | return null; 191 | } 192 | 193 | 194 | /** 195 | * {@inheritDoc} 196 | */ 197 | public function fileExists($fullFileName) 198 | { 199 | return file_exists($fullFileName); 200 | } 201 | 202 | 203 | 204 | 205 | 206 | 207 | } 208 | -------------------------------------------------------------------------------- /Form/DataTransformer/FileDataTransformer.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class FileDataTransformer implements DataTransformerInterface 17 | { 18 | 19 | const MODE_UPLOAD_FIELD = 'upload_field'; 20 | 21 | const MODE_FILEDATA_FIELD = 'filedata_field'; 22 | 23 | /** 24 | * @var \Iphp\FileStoreBundle\Mapping\PropertyMapping 25 | */ 26 | protected $mapping; 27 | 28 | /** 29 | * @var \Iphp\FileStoreBundle\FileStorage\FileStorageInterface 30 | */ 31 | protected $fileStorage; 32 | 33 | 34 | protected $mode; 35 | 36 | 37 | function __construct(FileStorageInterface $fileStorage) 38 | { 39 | $this->fileStorage = $fileStorage; 40 | } 41 | 42 | 43 | /** 44 | * Sets in PRE_BIND form event 45 | * @param PropertyMapping $mapping 46 | * @param $mode 47 | * @return $this 48 | */ 49 | public function setMapping(PropertyMapping $mapping, $mode) 50 | { 51 | $this->mapping = $mapping; 52 | $this->mode = $mode; 53 | return $this; 54 | } 55 | 56 | 57 | public function transform($fileDataFromDb) 58 | { 59 | return $fileDataFromDb; 60 | } 61 | 62 | 63 | /** 64 | * array with 2 items - file (UploadedFile) and delete (checkbox) 65 | * @param $fileDataFromForm 66 | * @return int 67 | */ 68 | public function reverseTransform($fileDataFromForm) 69 | { 70 | //if file field != file upload field - no need to store 'delete' in serialized file data 71 | if (isset($fileDataFromForm['delete']) && !$fileDataFromForm['delete']) 72 | unset($fileDataFromForm['delete']); 73 | 74 | 75 | if ($this->mapping && isset($fileDataFromForm['delete']) && $fileDataFromForm['delete']) { 76 | 77 | if ($this->mode == self::MODE_FILEDATA_FIELD) { 78 | return null; 79 | } 80 | 81 | //Todo: move to uploaderListener 82 | //File may no exists 83 | try { 84 | $this->fileStorage->removeFile($this->mapping->resolveFileName($fileDataFromForm['fileName'])); 85 | 86 | } catch (\Exception $e) { 87 | } 88 | 89 | } 90 | 91 | return isset($fileDataFromForm['file']) ? $fileDataFromForm['file'] : 92 | ($this->mode == self::MODE_UPLOAD_FIELD ? null : $fileDataFromForm); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Form/DataTransformer/FileDataViewTransformer.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class FileDataViewTransformer implements DataTransformerInterface 13 | { 14 | 15 | 16 | public function transform($fileDataFromDb) 17 | { 18 | return $fileDataFromDb; 19 | } 20 | 21 | 22 | /** 23 | * array with 2 items - file (UploadedFile) and delete (checkbox) 24 | * @param $fileDataFromForm 25 | * @return int 26 | */ 27 | public function reverseTransform($fileDataFromForm) 28 | { 29 | return $fileDataFromForm; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Form/Type/FileType.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class FileType extends AbstractType 27 | { 28 | 29 | /** 30 | * @var \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory 31 | */ 32 | protected $mappingFactory; 33 | 34 | /** 35 | * @var \Iphp\FileStoreBundle\DataStorage\DataStorageInterface 36 | */ 37 | protected $dataStorage; 38 | 39 | /** 40 | * @var \Iphp\FileStoreBundle\FileStorage\FileStorageInterface 41 | */ 42 | protected $fileStorage; 43 | 44 | public function __construct(PropertyMappingFactory $mappingFactory, 45 | DataStorageInterface $dataStorage, 46 | FileStorageInterface $fileStorage) 47 | { 48 | $this->mappingFactory = $mappingFactory; 49 | $this->dataStorage = $dataStorage; 50 | $this->fileStorage = $fileStorage; 51 | } 52 | 53 | public function configureOptions (OptionsResolver $resolver) 54 | { 55 | $resolver->setDefaults(array( 56 | 'read_only' => false, 57 | 'upload' => true, 58 | 'show_uploaded' => true, 59 | 'show_preview' => true 60 | )); 61 | } 62 | 63 | public function setDefaultOptions(OptionsResolverInterface $resolver) 64 | { 65 | $resolver->setDefaults(array( 66 | 'read_only' => false, 67 | 'upload' => true, 68 | 'show_uploaded' => true, 69 | 'show_preview' => true 70 | )); 71 | } 72 | 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function buildForm(FormBuilderInterface $builder, array $options) 78 | { 79 | 80 | $transformer = new FileDataTransformer($this->fileStorage); 81 | $subscriber = new FileTypeBindSubscriber( 82 | $this->mappingFactory, 83 | $this->dataStorage, 84 | $transformer, 85 | $options); 86 | $builder->addEventSubscriber($subscriber); 87 | 88 | 89 | // $builder->add('file', 'file', array('required' => false)) 90 | // ->add('delete', 'checkbox', array('required' => false)) 91 | $builder->addViewTransformer($transformer); 92 | 93 | 94 | 95 | //for sonata admin 96 | // ->addViewTransformer(new FileDataViewTransformer()); 97 | } 98 | 99 | //for using iphp_file_widget from Resources/views/Form/fields.html.twig 100 | public function getBlockPrefix() 101 | { 102 | return 'iphp_file'; 103 | } 104 | 105 | 106 | public function buildView(FormView $view, FormInterface $form, array $options) 107 | { 108 | $view->vars['upload'] = $options['upload']; 109 | $view->vars['show_preview'] = $options['show_preview']; 110 | 111 | $view->vars['file_data'] = $view->vars['value']; 112 | 113 | } 114 | 115 | 116 | } 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /Form/Type/FileTypeBindSubscriber.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class FileTypeBindSubscriber implements EventSubscriberInterface 15 | { 16 | 17 | /** 18 | * @var \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory 19 | */ 20 | private $mappingFactory; 21 | 22 | /** 23 | * @var \Symfony\Component\Form\DataTransformerInterface 24 | */ 25 | private $transformer; 26 | 27 | 28 | /** 29 | * @var \Iphp\FileStoreBundle\DataStorage\DataStorageInterface 30 | */ 31 | private $dataStorage; 32 | 33 | public function __construct(PropertyMappingFactory $mappingFactory, 34 | DataStorageInterface $dataStorage, 35 | FileDataTransformer $transformer, 36 | array $options = array()) 37 | { 38 | $this->mappingFactory = $mappingFactory; 39 | $this->dataStorage = $dataStorage; 40 | $this->transformer = $transformer; 41 | } 42 | 43 | public static function getSubscribedEvents() 44 | { 45 | return array( 46 | FormEvents::PRE_SUBMIT => 'preBind', 47 | FormEvents::PRE_SET_DATA => 'preSet'); 48 | } 49 | 50 | 51 | public function preSet(FormEvent $event) 52 | { 53 | $form = $event->getForm(); 54 | $propertyName = $form->getName(); 55 | 56 | $obj = $form->getParent()->getData(); 57 | 58 | if (!$obj) return; 59 | 60 | $mapping = $this->mappingFactory->getMappingFromField($obj, 61 | $this->dataStorage->getReflectionClass($obj), 62 | $propertyName); 63 | 64 | if ($mapping) { 65 | if ($propertyName == $mapping->getFileUploadPropertyName()) 66 | $form->add('file', \Symfony\Component\Form\Extension\Core\Type\FileType::class, ['required' => false]); 67 | 68 | if ($propertyName == $mapping->getFileDataPropertyName()) 69 | $form->add('delete', \Symfony\Component\Form\Extension\Core\Type\CheckboxType::class, ['required' => false]); 70 | } 71 | } 72 | 73 | public function preBind(FormEvent $event) 74 | { 75 | $form = $event->getForm(); 76 | $propertyName = $form->getName(); 77 | $obj = $form->getParent()->getData(); 78 | 79 | if (!$obj) return; 80 | 81 | $mapping = $this->mappingFactory->getMappingFromField($obj, 82 | $this->dataStorage->getReflectionClass($obj), 83 | $propertyName); 84 | 85 | if ($mapping) { 86 | $this->transformer->setMapping($mapping, 87 | $mapping->getFileUploadPropertyName() == $propertyName ? 88 | FileDataTransformer::MODE_UPLOAD_FIELD : FileDataTransformer::MODE_FILEDATA_FIELD 89 | ); 90 | } 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /IphpFileStoreBundle.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class IphpFileStoreBundle extends Bundle 12 | { 13 | 14 | public function build(ContainerBuilder $container) 15 | { 16 | parent::build($container); 17 | $container->addCompilerPass(new FormPass()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Mapping/Annotation/Uploadable.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Uploadable 13 | { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Mapping/Annotation/UploadableField.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UploadableField 13 | { 14 | /** 15 | * @var string $mapping 16 | */ 17 | protected $mapping; 18 | 19 | /** 20 | * @var string $name 21 | */ 22 | protected $fileUploadPropertyName; 23 | 24 | /** 25 | * @var string $fileNameProperty 26 | */ 27 | protected $fileDataPropertyName; 28 | 29 | /** 30 | * Constructs a new instance of UploadableField. 31 | * 32 | * @param array $options The options. 33 | */ 34 | public function __construct(array $options) 35 | { 36 | if (isset($options['mapping'])) { 37 | $this->mapping = $options['mapping']; 38 | } else { 39 | throw new \InvalidArgumentException('The "mapping" attribute of UploadableField is required.'); 40 | } 41 | if (isset($options['fileDataProperty'])) 42 | { 43 | $this->setFileDataPropertyName($options['fileDataProperty']); 44 | } 45 | } 46 | 47 | /** 48 | * Gets the mapping name. 49 | * 50 | * @return string The mapping name. 51 | */ 52 | public function getMapping() 53 | { 54 | return $this->mapping; 55 | } 56 | 57 | /** 58 | * Sets the mapping name. 59 | * 60 | * @param $mapping The mapping name. 61 | */ 62 | public function setMapping($mapping) 63 | { 64 | $this->mapping = $mapping; 65 | } 66 | 67 | /** 68 | * Gets the property name. 69 | * 70 | * @return string The property name. 71 | */ 72 | public function getFileUploadPropertyName() 73 | { 74 | return $this->fileUploadPropertyName; 75 | } 76 | 77 | /** 78 | * Sets the property name. 79 | * 80 | * @param $propertyName The property name. 81 | */ 82 | public function setFileUploadPropertyName($propertyName) 83 | { 84 | $this->fileUploadPropertyName = $propertyName; 85 | } 86 | 87 | /** 88 | * Gets the file name property. 89 | * By default using propertyName 90 | * @return string The file name property. 91 | */ 92 | public function getFileDataPropertyName() 93 | { 94 | return $this->fileDataPropertyName ? $this->fileDataPropertyName : $this->fileUploadPropertyName; 95 | } 96 | 97 | /** 98 | * Sets the file data property name. 99 | * 100 | * @param $fileNameProperty The file name property. 101 | */ 102 | public function setFileDataPropertyName ($fileDataPropertyName) 103 | { 104 | $this->fileDataPropertyName = $fileDataPropertyName; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Mapping/PropertyMapping.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | */ 17 | class PropertyMapping 18 | { 19 | 20 | 21 | protected $obj; 22 | 23 | /** 24 | * @var array $config 25 | */ 26 | protected $config; 27 | 28 | /** 29 | * @var \Iphp\FileStoreBundle\Naming\NamerServiceInvoker $namerServiceInvoker 30 | */ 31 | protected $namerServiceInvoker; 32 | 33 | /** 34 | * @var \ReflectionProperty $property reflection property that represents the annotated property 35 | */ 36 | protected $fileUploadProperty; 37 | 38 | /** 39 | * @var \ReflectionProperty $fileNameProperty reflection property that represents property which holds data 40 | */ 41 | protected $fileDataProperty; 42 | 43 | /** 44 | * @var string $mappingName 45 | */ 46 | protected $mappingName; 47 | 48 | 49 | function __construct($obj, $config, NamerServiceInvoker $namerServiceInvoker) 50 | { 51 | $this->obj = $obj; 52 | $this->setConfig($config); 53 | $this->namerServiceInvoker = $namerServiceInvoker; 54 | } 55 | 56 | 57 | /** 58 | * Sets the reflection property that represents the annotated 59 | * property. 60 | * 61 | * @param \ReflectionProperty $property The reflection property. 62 | */ 63 | public function setFileUploadProperty(\ReflectionProperty $property) 64 | { 65 | $this->fileUploadProperty = $property; 66 | $this->fileUploadProperty->setAccessible(true); 67 | } 68 | 69 | 70 | /** 71 | * Sets the reflection property that represents the property 72 | * which holds the file name for the mapping. 73 | * 74 | * @param \ReflectionProperty $fileNameProperty The reflection property. 75 | */ 76 | public function setFileDataProperty(\ReflectionProperty $fileNameProperty) 77 | { 78 | $this->fileDataProperty = $fileNameProperty; 79 | $this->fileDataProperty->setAccessible(true); 80 | } 81 | 82 | 83 | /** 84 | * Invoke file namers 85 | * @param $fileName 86 | * @return mixed 87 | */ 88 | public function useFileNamer($fileName) 89 | { 90 | if ($this->hasNamer()) { 91 | foreach ($this->config['namer'] as $method => $namer) { 92 | $fileName = $this->namerServiceInvoker->rename($namer['service'], $method, $this, $fileName, 93 | isset($namer['params']) ? $namer['params'] : array()); 94 | } 95 | } 96 | return $fileName; 97 | } 98 | 99 | 100 | /** 101 | * Determines if the mapping has a custom namer configured. 102 | * 103 | * @return bool True if has namer, false otherwise. 104 | */ 105 | public function hasNamer() 106 | { 107 | return isset($this->config['namer']) && $this->config['namer']; 108 | } 109 | 110 | 111 | /** 112 | * Determines if the mapping requires store full path to file 113 | * @return bool 114 | */ 115 | public function isStoreFullDir() 116 | { 117 | return isset($this->config['store_fulldir']) && $this->config['store_fulldir']; 118 | } 119 | 120 | 121 | /** 122 | * Determines if the mapping has a custom directory namer configured. 123 | * 124 | * @return bool True if has directory namer, false otherwise. 125 | */ 126 | public function hasDirectoryNamer() 127 | { 128 | return isset($this->config['directory_namer']) && $this->config['directory_namer']; 129 | } 130 | 131 | /** 132 | * create subdirectory name based on chain of directory namers 133 | * 134 | * @return array directory name and web path to file 135 | */ 136 | public function useDirectoryNamer($fileName, $clientOriginalName) 137 | { 138 | $path = ''; 139 | 140 | if ($this->hasDirectoryNamer()) { 141 | foreach ($this->config['directory_namer'] as $method => $namer) { 142 | 143 | $replaceMode = $method == 'replace' || 144 | (isset($namer['params']['replace']) && $namer['params']['replace']); 145 | 146 | 147 | $subPath = $this->namerServiceInvoker->rename($namer['service'], 148 | $method, 149 | $this, 150 | $replaceMode ? $path : $fileName, 151 | isset($namer['params']) ? $namer['params'] : array()); 152 | 153 | 154 | /* $subPath = call_user_func( 155 | array($this->container->get($namer['service']), $method . 'Rename'), 156 | $this, 157 | $replaceMode ? $path : $fileName, 158 | isset($namer['params']) ? $namer['params'] : array());*/ 159 | 160 | 161 | if ($replaceMode) $path = $subPath; 162 | else $path .= ($subPath ? '/' : '') . $subPath; 163 | } 164 | 165 | } 166 | 167 | return $path; 168 | } 169 | 170 | 171 | public function needResolveCollision($fileName, FileStorageInterface $fileStorage) 172 | { 173 | //print "\n -->".$fileName; 174 | return !$this->isOverwriteDuplicates() && $fileStorage->fileExists($this->resolveFileName($fileName)); 175 | } 176 | 177 | 178 | /** 179 | * @param $originalName 180 | * @param \Iphp\FileStoreBundle\FileStorage\FileStorageInterface $fileStorage 181 | * @return array relative or full fileName and file path at web 182 | * @throws \Exception 183 | */ 184 | public function prepareFileName($originalName, FileStorageInterface $fileStorage) 185 | { 186 | $fileName = $origName = $this->useFileNamer($originalName); 187 | $dirName = $this->useDirectoryNamer($fileName, $originalName); 188 | if (substr($dirName,-1) != '/') $dirName.='/'; 189 | 190 | $try = 0; 191 | 192 | while ($this->needResolveCollision( $dirName . $fileName , $fileStorage)) { 193 | if ($try > 15) 194 | throw new \Exception ("Can't resolve collision for file " . $fileName); 195 | 196 | $fileName = $this->resolveFileCollision($origName, $originalName, ++$try); 197 | } 198 | 199 | 200 | return array( 201 | //file system path 202 | ($this->isStoreFullDir() ? $this->getUploadDir() : '') . $dirName. $fileName , 203 | //web path 204 | $this->getUploadPath() ? $this->getUploadPath() . $dirName . urlencode($fileName) : ''); 205 | } 206 | 207 | 208 | /** 209 | * @param $fileName 210 | * @param $clientOriginalName 211 | * @param int $attempt 212 | * @return string new file path 213 | * @throws \Exception 214 | */ 215 | public function resolveFileCollision($fileName, $clientOriginalName, $attempt = 1) 216 | { 217 | 218 | if ($this->hasNamer()) { 219 | $firstNamer = current($this->config['namer']); 220 | 221 | 222 | return $this->namerServiceInvoker->resolveCollision ($firstNamer['service'], $fileName, $attempt); 223 | } 224 | 225 | throw new \Exception ('Filename resolving collision not supported (namer is empty).Duplicate filename ' . $fileName); 226 | } 227 | 228 | 229 | public function getUploadDir() 230 | { 231 | return $this->config['upload_dir']; 232 | } 233 | 234 | 235 | public function getUploadPath() 236 | { 237 | return $this->config['upload_path']; 238 | } 239 | 240 | 241 | /** 242 | * Sets the configured configuration mapping. 243 | * 244 | * @param array $mapping The mapping; 245 | */ 246 | public function setConfig(array $config) 247 | { 248 | $this->config = $config; 249 | } 250 | 251 | /** 252 | * Gets the configured configuration mapping name. 253 | * 254 | * @return string The mapping name. 255 | */ 256 | public function getMappingName() 257 | { 258 | return $this->mappingName; 259 | } 260 | 261 | /** 262 | * Sets the configured configuration mapping name. 263 | * 264 | * @param $mappingName The mapping name. 265 | */ 266 | public function setMappingName($mappingName) 267 | { 268 | $this->mappingName = $mappingName; 269 | } 270 | 271 | /** 272 | * Gets the name of the annotated property. 273 | * 274 | * @return string The name. 275 | */ 276 | public function getFileUploadPropertyName() 277 | { 278 | return $this->fileUploadProperty->getName(); 279 | } 280 | 281 | /** 282 | * Gets the value of the annotated property. 283 | * @return \Symfony\Component\HttpFoundation\File\UploadedFile 284 | */ 285 | public function getFileUploadPropertyValue() 286 | { 287 | return $this->fileUploadProperty->getValue($this->obj); 288 | } 289 | 290 | 291 | public function setFileUploadPropertyValue($file) 292 | { 293 | $this->fileUploadProperty->setValue($this->obj, $file); 294 | } 295 | 296 | public function setFileDataPropertyValue($fileData) 297 | { 298 | $this->fileDataProperty->setValue($this->obj, $fileData); 299 | } 300 | 301 | 302 | public function getFileDataPropertyValue() 303 | { 304 | return $this->fileDataProperty->getValue($this->obj); 305 | } 306 | 307 | 308 | /** 309 | * Gets the configured file name property name. 310 | * 311 | * @return string The name. 312 | */ 313 | public function getFileDataPropertyName() 314 | { 315 | return $this->fileDataProperty->getName(); 316 | } 317 | 318 | /** 319 | * Property for upload and property for file data is one property 320 | * @return bool 321 | */ 322 | public function isUseOneProperty() 323 | { 324 | 325 | return $this->getFileDataPropertyName() == $this->getFileUploadPropertyName() ? true : false; 326 | } 327 | 328 | 329 | 330 | /** 331 | * Determines if the file should be deleted upon removal of the 332 | * entity. Default true 333 | * 334 | * @return bool True if delete on remove, false otherwise. 335 | */ 336 | public function getDeleteOnRemove() 337 | { 338 | return !isset($this->config['delete_on_remove']) || $this->config['delete_on_remove']; 339 | } 340 | 341 | 342 | /** 343 | * @return bool True if overwrite file duplicates, if false - using resolve collision 344 | */ 345 | public function isOverwriteDuplicates() 346 | { 347 | return isset($this->config['overwrite_duplicates']) && $this->config['overwrite_duplicates']; 348 | } 349 | 350 | 351 | public function getObj() 352 | { 353 | return $this->obj; 354 | } 355 | 356 | 357 | public function resolveFileName($fileName = null) 358 | { 359 | if (!$fileName) { 360 | $fileData = $this->getFileDataPropertyValue(); 361 | if ($fileData && isset($fileData['fileName'])) $fileName = $fileData['fileName']; 362 | } 363 | if (!$fileName) return null; 364 | 365 | $dir = $this->isStoreFullDir() ? '' : $this->getUploadDir(); 366 | return $dir . (substr($dir,-1) != '/' && substr($fileName,0,1) != '/' ? '/' : ''). $fileName; 367 | } 368 | 369 | 370 | } 371 | -------------------------------------------------------------------------------- /Mapping/PropertyMappingFactory.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class PropertyMappingFactory 17 | { 18 | /** 19 | * @var \Iphp\FileStoreBundle\Naming\NamerServiceInvoker $namerServiceInvoker 20 | */ 21 | protected $namerServiceInvoker; 22 | 23 | /** 24 | * @var \Iphp\FileStoreBundle\Driver\AnnotationDriver $driver 25 | */ 26 | protected $driver; 27 | 28 | 29 | /** 30 | * @var array $mappingsConfig MappingConfiguration 31 | */ 32 | protected $mappingsConfig = array(); 33 | 34 | /** 35 | * Constructs a new instance of PropertyMappingFactory. 36 | * 37 | * @param \Iphp\FileStoreBundle\Naming\NamerServiceInvoker $namerServiceInvoker Object for invoke rename methods. 38 | * @param \Iphp\FileStoreBundle\Driver\AnnotationDriver $driver The driver. 39 | * @param array $mappings The configured mappings. 40 | */ 41 | public function __construct(NamerServiceInvoker $namerServiceInvoker, 42 | AnnotationDriver $driver, 43 | array $mappingsConfig) 44 | { 45 | $this->namerServiceInvoker = $namerServiceInvoker; 46 | $this->driver = $driver; 47 | $this->mappingsConfig = $mappingsConfig; 48 | } 49 | 50 | 51 | /** 52 | * Creates an array of PropetyMapping objects which contain the 53 | * configuration for the uploadable fields in the specified 54 | * object. 55 | * 56 | * @param object $obj The object. 57 | * @param \ReflectionClass $class 58 | * @return \Iphp\FileStoreBundle\Mapping\PropertyMapping[] objects. 59 | */ 60 | public function getMappingsFromObject($obj, \ReflectionClass $class) 61 | { 62 | if (!$this->hasAnnotations($class)) return array(); 63 | 64 | $mappings = array(); 65 | foreach ($this->driver->readUploadableFields($class) as $field) { 66 | $mappings[] = $this->createMapping($obj, $class, $field); 67 | } 68 | 69 | return $mappings; 70 | } 71 | 72 | 73 | /** 74 | * Creates a property mapping object which contains the 75 | * configuration for the specified uploadable field. 76 | * 77 | * @param object $obj The object. 78 | * @param \ReflectionClass $class 79 | * @param string $field entity field name 80 | * @param bool $allFields search all fields (if upload field and file data field are separate) 81 | * @return null|\Iphp\FileStoreBundle\Mapping\PropertyMapping The property mapping. 82 | */ 83 | public function getMappingFromField($obj, \ReflectionClass $class, $field, $allFields = true) 84 | { 85 | if (!$this->hasAnnotations($class)) return null; 86 | 87 | $annotation = $this->driver->readUploadableField($class, $field); 88 | 89 | if (!$annotation && $allFields) { 90 | $propertyAnnotations= $this->driver->readUploadableFields($class); 91 | 92 | foreach ($propertyAnnotations as $propertyAnnotation) 93 | { 94 | if ($propertyAnnotation->getFileDataPropertyName() == $field || 95 | $propertyAnnotation->getFileUploadPropertyName() == $field) 96 | { 97 | $annotation = $propertyAnnotation; 98 | break; 99 | } 100 | } 101 | } 102 | if (null === $annotation) return null; 103 | 104 | return $this->createMapping($obj, $class, $annotation); 105 | } 106 | 107 | public function hasAnnotations(\ReflectionClass $class) 108 | { 109 | return null !== $this->driver->readUploadable($class); 110 | } 111 | 112 | /** 113 | * Creates the property mapping from the read annotation and configured mapping. 114 | * 115 | * @param object $obj The object. 116 | * @param \ReflectionClass $class 117 | * @param \Iphp\FileStoreBundle\Mapping\Annotation\UploadableField $field The read annotation. 118 | * @return \Iphp\FileStoreBundle\Mapping\PropertyMapping The property mapping. 119 | * @throws \InvalidArgumentException 120 | */ 121 | protected function createMapping($obj, \ReflectionClass $class, UploadableField $field) 122 | { 123 | if (!array_key_exists($field->getMapping(), $this->mappingsConfig)) { 124 | throw new \InvalidArgumentException(sprintf( 125 | 'No mapping named "%s" configured.', $field->getMapping() 126 | )); 127 | } 128 | 129 | $config = $this->mappingsConfig[$field->getMapping()]; 130 | 131 | $mapping = new PropertyMapping($obj, $config, $this->namerServiceInvoker); 132 | $mapping->setFileUploadProperty($class->getProperty($field->getFileUploadPropertyName())); 133 | $mapping->setFileDataProperty($class->getProperty($field->getFileDataPropertyName())); 134 | 135 | $mapping->setMappingName($field->getMapping()); 136 | 137 | 138 | return $mapping; 139 | } 140 | 141 | 142 | } 143 | -------------------------------------------------------------------------------- /Naming/DefaultDirectoryNamer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DefaultDirectoryNamer 11 | { 12 | 13 | 14 | /** 15 | * 16 | */ 17 | function propertyRename(PropertyMapping $propertyMapping, $fileName, $params) 18 | { 19 | 20 | if (isset($params['use_field_name']) && $params['use_field_name']) 21 | return $propertyMapping->getFileDataPropertyName(); 22 | 23 | $obj = $propertyMapping->getObj(); 24 | $field = isset($params['field']) && $params['field'] ? $params['field'] : 'id'; 25 | 26 | 27 | $fields = explode('/', $field); 28 | $path = ''; 29 | 30 | 31 | foreach ($fields as $f) { 32 | if (strpos($f, '.')) { 33 | $str = 'return $obj->get' . implode('()->get', array_map('ucfirst', explode('.', $f))) . '();'; 34 | $fieldValue = eval ($str); 35 | } else $fieldValue = $obj->{'get' . ucfirst($f)}(); 36 | $path .= ($path ? '/' : '') . $fieldValue; 37 | } 38 | 39 | return $path; 40 | } 41 | 42 | 43 | function entityNameRename(PropertyMapping $propertyMapping, $fileName, $params) 44 | { 45 | return implode('', array_slice(explode('\\', get_class($propertyMapping->getObj())), -1)); 46 | } 47 | 48 | 49 | function replaceRename(PropertyMapping $propertyMapping, $name, $params) 50 | { 51 | return strtr($name, $params); 52 | } 53 | 54 | 55 | function dateRename(PropertyMapping $propertyMapping, $fileName, $params) 56 | { 57 | $obj = $propertyMapping->getObj(); 58 | 59 | $field = isset($params['field']) && $params['field'] ? $params['field'] : 'id'; 60 | $depth = isset($params['depth']) && $params['depth'] ? strtolower($params['depth']) : 'day'; 61 | 62 | $date = $obj->{'get' . ucfirst($field)}(); 63 | $date = $date ? $date->getTimestamp() : time(); 64 | 65 | $tpl = "Y/m/d"; 66 | if ($depth == 'month') $tpl = "Y/m"; 67 | if ($depth == 'year') $tpl = "Y"; 68 | 69 | $dirName = date($tpl, $date); 70 | 71 | return $dirName; 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Naming/DefaultNamer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DefaultNamer 11 | { 12 | /** 13 | * Filename translitaration renamin 14 | * @param \Iphp\FileStoreBundle\Mapping\PropertyMapping $propertyMapping 15 | * @param $name 16 | * @return string 17 | */ 18 | public function translitRename(PropertyMapping $propertyMapping, $name) 19 | { 20 | $name = transliterator_transliterate("Any-Latin; Latin-ASCII; [\u0100-\u7fff] remove", $name); 21 | $name = preg_replace('/[^\\pL\d.]+/u', '-', $name); 22 | $name = preg_replace('/[-\s]+/', '-', $name); 23 | $name = strtolower(trim($name, '-')); 24 | 25 | return $name; 26 | } 27 | 28 | /** 29 | * Rename file name based on value of object property (default: id) 30 | * @param \Iphp\FileStoreBundle\Mapping\PropertyMapping $propertyMapping 31 | * @param $name 32 | * @param $params 33 | * @return string 34 | */ 35 | public function propertyRename(PropertyMapping $propertyMapping, $name, $params) 36 | { 37 | $fieldValue = $this->getFieldValueByParam($propertyMapping, $params); 38 | if ($fieldValue) $name = $fieldValue . substr($name, strrpos($name, '.')); 39 | return $name; 40 | } 41 | 42 | protected function getFieldValueByParam(PropertyMapping $propertyMapping, $params) 43 | { 44 | $obj = $propertyMapping->getObj(); 45 | 46 | $fieldValue = ''; 47 | if (isset($params['use_field_name']) && $params['use_field_name']) { 48 | $fieldValue = $propertyMapping->getFileDataPropertyName(); 49 | } else { 50 | $field = isset($params['field']) && $params['field'] ? $params['field'] : 'id'; 51 | $fieldValue = $obj->{'get' . ucfirst($field)}(); 52 | } 53 | 54 | if (!$fieldValue) $fieldValue = $obj->getId(); 55 | return $fieldValue; 56 | } 57 | 58 | public function propertyPrefixRename(PropertyMapping $propertyMapping, $name, $params) 59 | { 60 | $fieldValue = $this->getFieldValueByParam($propertyMapping, $params); 61 | $delimiter = isset($params['delimiter']) && $params['delimiter'] ? $params['delimiter'] : '-'; 62 | 63 | return $fieldValue . $delimiter . $name; 64 | } 65 | 66 | public function propertyPostfixRename(PropertyMapping $propertyMapping, $name, $params) 67 | { 68 | $fieldValue = $this->getFieldValueByParam($propertyMapping, $params); 69 | $delimiter = isset($params['delimiter']) && $params['delimiter'] ? $params['delimiter'] : '-'; 70 | 71 | $ppos = strrpos($name, '.'); 72 | return substr($name, 0, $ppos) . $delimiter . $fieldValue . '' . substr($name, $ppos); 73 | 74 | } 75 | 76 | public function replaceRename(PropertyMapping $propertyMapping, $name, $params) 77 | { 78 | return strtr($name, $params); 79 | } 80 | 81 | /** 82 | * Разрешение коллизий с одинаковыми названиями файлов 83 | * 84 | * @param $name 85 | * @param int $attempt 86 | * @return string 87 | */ 88 | public function resolveCollision($name, $attempt = 1) 89 | { 90 | $addition = $attempt; 91 | if ($attempt > 10) $addition = date('Y_m_d_H_i_s'); 92 | 93 | $ppos = strrpos($name, '.'); 94 | 95 | return ($ppos === false ? $name : substr($name, 0, $ppos)) 96 | . '_' . $addition . '' 97 | . ($ppos === false ? '' : substr($name, $ppos)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Naming/NamerServiceInvoker.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class NamerServiceInvoker 11 | { 12 | 13 | protected $container; 14 | 15 | public function __construct(ContainerInterface $container) 16 | { 17 | $this->container = $container; 18 | } 19 | 20 | 21 | public function rename($serviceName, $method, PropertyMapping $propertyMapping, $fileName, $args = array()) 22 | { 23 | 24 | return call_user_func( 25 | array($this->container->get($serviceName), $method . 'Rename'), 26 | $propertyMapping, 27 | $fileName, 28 | $args); 29 | 30 | } 31 | 32 | 33 | public function resolveCollision ($serviceName, $fileName, $attempt) 34 | { 35 | return call_user_func( 36 | array($this->container->get($serviceName), 'resolveCollision'), $fileName, $attempt); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IphpFileStoreBundle - Symfony 2 Doctrine ORM file upload bundle 2 | =================== 3 | 4 | [![Build Status](https://api.travis-ci.org/vitiko/IphpFileStoreBundle.png?branch=master)](http://travis-ci.org/vitiko/IphpFileStoreBundle) 5 | [![Total Downloads](https://poser.pugx.org/iphp/filestore-bundle/downloads.png)](https://packagist.org/packages/iphp/filestore-bundle) 6 | [![Code Climate](https://codeclimate.com/github/vitiko/IphpFileStoreBundle/badges/gpa.svg)](https://codeclimate.com/github/vitiko/IphpFileStoreBundle) 7 | 8 | The IphpFileStoreBundle is a Symfony2 bundle that automates file uploads that are attached to an entity. 9 | The bundle will automatically name and save the uploaded file according to the configuration specified on a per property 10 | basis using a mix of configuration and annotations. 11 | After the entity has been created and the file has been saved, array with data of uploaded file 12 | will be saved to according property. 13 | The bundle provide different ways to naming uploaded files and directories. 14 | 15 | For Russian documentation see http://symfonydev.ru/iphpfilestorebundle/ 16 | 17 | 18 | ## Installation 19 | 20 | ### Get the bundle 21 | 22 | 23 | Add the following lines in your composer.json: 24 | ``` 25 | { 26 | "require": { 27 | "iphp/filestore-bundle" : "@stable" 28 | } 29 | } 30 | ``` 31 | 32 | ### Initialize the bundle 33 | 34 | To start using the bundle, register the bundle in your application's kernel class: 35 | 36 | ``` php 37 | // app/AppKernel.php 38 | public function registerBundles() 39 | { 40 | $bundles = array( 41 | // ... 42 | new Iphp\FileStoreBundle\IphpFileStoreBundle(), 43 | ); 44 | ) 45 | ``` 46 | 47 | 48 | ## Usage 49 | 50 | IphpFileStoreBundle try to handle file uploads according to a combination 51 | of configuration parameters and annotations. In order to have your upload 52 | working you have to: 53 | 54 | * Define a basic configuration set 55 | * Annotate your Entities 56 | 57 | 58 | ### Configuration 59 | 60 | ``` yaml 61 | # app/config/config.yml 62 | iphp_file_store: 63 | mappings: 64 | photo: 65 | upload_dir: %kernel.root_dir%/../web/photo 66 | upload_path: /photo 67 | ``` 68 | 69 | The `upload_dir` and `upload_path` is the only required configuration options for an entity mapping. 70 | 71 | All options are listed below: 72 | 73 | - `upload_dir`: directory to upload the file to 74 | - `upload_path`: web path of upload dir 75 | - `namer`: configuration of file naming (See [Namers](#namers) section below) 76 | - `directory_namer`: configuration of directory naming 77 | - `delete_on_remove`: Set to true if the file should be deleted from the 78 | filesystem when the entity is removed 79 | - `overwrite_duplicates`: Set to true if the file with same name will be overwritten by a new file. 80 | In another case (by default), to the name of the new file will be added extra digits 81 | 82 | 83 | 84 | ### Annotate Entities 85 | 86 | In order for your entity to work with the bundle, you need to add a 87 | few annotations to it. First, annotate your class with the `Uploadable` annotation. 88 | This lets the bundle know that it should look for files to upload in your class when 89 | it is saved, inject the files when it is loaded and check to see if it needs to 90 | remove files when it is removed. Next, you should annotate the fields which hold 91 | the instance of `Symfony\Component\HttpFoundation\File\UploadedFile` when the form 92 | is submitted with the `UploadableField` annotation. The `UploadableField` annotation 93 | has a few required options. They are as follows: 94 | 95 | - `mapping`: The mapping specified in the bundle configuration to use 96 | - `fileDataProperty`: name of field, where are stored file data 97 | 98 | 99 | Lets look at an example using a fictional `Photo` ORM entity: 100 | 101 | #### recommended use case - upload in one field (uploadPhoto), store file data in another field (photo) 102 | 103 | 104 | ``` php 105 | getPhoto()['path']; 211 | ``` 212 | 213 | or in a Twig template: 214 | 215 | ``` html 216 | {{ photo.title}} 217 | ``` 218 | 219 | Example of using entities with uploadable can be seen in [controller](https://github.com/vitiko/IphpFileStoreBundle/blob/master/Tests/Functional/TestBundle/Controller/DefaultController.php) 220 | and twig template [for uploading](https://github.com/vitiko/IphpFileStoreBundle/blob/master/Tests/Functional/TestBundle/Resources/views/Photo/index.html.twig) 221 | and [editing](https://github.com/vitiko/IphpFileStoreBundle/blob/master/Tests/Functional/TestBundle/Resources/views/Photo/edit.html.twig) entities. 222 | 223 | 224 | ###Example of interface with list of uploaded photos 225 | 226 | ![interface with list of uploaded photos](https://raw.github.com/vitiko/IphpFileStoreBundle/master/Tests/Fixtures/images/front-images-list.jpeg) 227 | 228 | 229 | 230 | 231 | ## Using form field type 232 | 233 | Form field type `Iphp\FileStoreBundle\Form\Type\FileType` can be used in admin class, created for SonataAdminBundle. 234 | If entity already has uploaded file - information about this file will be displayed. Also 235 | delete checkbox allows to delete uploaded file. 236 | 237 | ``` php 238 | addIdentifier('title') 252 | ->add ('date'); 253 | } 254 | 255 | protected function configureFormFields(FormMapper $formMapper) 256 | { 257 | return $formMapper->add('title') 258 | ->add ('date') 259 | ->add('photo', IphpFileType::class); 260 | } 261 | } 262 | ``` 263 | 264 | ### Example of sonata admin form for uploaded photo 265 | ![Example of edit form for uploaded photo](https://raw.github.com/vitiko/IphpFileStoreBundle/master/Tests/Fixtures/images/sonata-admin-iphpfile.jpeg) 266 | 267 | ## Customizing form field 268 | 269 | For example we want show preview of original uploaded image, preview generated with LiipImagineBundle https://github.com/liip/LiipImagineBundle. For change `` of preview we need to modify original form field template wich defined in https://github.com/vitiko/IphpFileStoreBundle/blob/master/Resources/views/Form/fields.html.twig. 270 | 271 | When customizing the form field block in Twig, you have two options on where the customized form block can live: 272 | 273 | ### Method 1: Form theming 274 | 275 | The easiest way to customize the `iphp_file_widget` block is to customize it directly in the template that's actually rendering the form. 276 | 277 | ``` twig 278 | {% form_theme form _self %} 279 | 280 | {% block iphp_file_widget_image_preview %} 281 |
282 | 283 | 284 | 285 |
286 | 287 |
{{ file_data.width ~ 'x' ~ file_data.height }}
288 | {% endblock iphp_file_widget_image_preview %} 289 | ``` 290 | more info about form customization here http://symfony.com/doc/current/form/form_customization.html#form-theming 291 | 292 | ### Method 2: Override bundle template 293 | 294 | To override the bundle template, just copy the field.html.twig template from the vendor/iphp/filestore-bundle/Iphp/FileStoreBundle/Resources/views/Form/fields.html.twig to app/Resources/IphpFileStoreBundle/views/Form/fields.html.twig (the app/Resources/IphpFileStoreBundle directory won't exist, so you'll need to create it). You're now free to customize the template. 295 | 296 | for example, for display preview in all forms 297 | ``` twig 298 | {#app/Resources/IphpFileStoreBundle/views/Form/fields.html.twig#} 299 | {% extends 'IphpFileStoreBundle:Form:fields-base.html.twig' %} 300 | 301 | {% block iphp_file_widget_image_preview %} 302 |
303 | 304 | 305 | 306 |
307 | 308 |
{{ file_data.width ~ 'x' ~ file_data.height }}
309 | {% endblock iphp_file_widget_image_preview %} 310 | ``` 311 | 312 | more info about overriding templates from third-party bundle here http://symfony.com/doc/current/templating/overriding.html 313 | 314 | 315 | ## Namers 316 | 317 | The bundle uses namers to name the files and directories it saves to the filesystem. If no namer is 318 | configured for a mapping, the bundle will use default transliteration namer for files was uploaded. 319 | if you would like to change this then you can use one of the provided namers or implement a custom one. 320 | 321 | ### File Namers 322 | 323 | 324 | #### Translit 325 | 326 | Transliteration - replace cyrillic and other chars to ascii 327 | 328 | ``` yaml 329 | # app/config/config.yml 330 | iphp_file_store: 331 | mappings: 332 | some_entity: 333 | ... 334 | namer: ~ // default 335 | ``` 336 | 337 | 338 | To cancel transliteration 339 | ``` yaml 340 | # app/config/config.yml 341 | iphp_file_store: 342 | mappings: 343 | some_entity: 344 | ... 345 | namer: false 346 | ``` 347 | 348 | #### Using entity field value 349 | 350 | File name by value of entity field ( field name - title) 351 | ``` yaml 352 | # app/config/config.yml 353 | iphp_file_store: 354 | mappings: 355 | some_entity: 356 | namer: 357 | property: 358 | params: { field : title } 359 | translit: ~ 360 | ``` 361 | 362 | #### Adding entity field value 363 | 364 | Adding to the beginnng (propertyPrefix) or end (propertyPostfix) of file name value of entity field 365 | ``` yaml 366 | # app/config/config.yml 367 | iphp_file_store: 368 | mappings: 369 | some_entity: 370 | namer: 371 | translit: ~ 372 | propertyPrefix: #or propertyPostfix 373 | params: { field : id, delimiter: "_" } 374 | ``` 375 | 376 | #### Using entity field name 377 | 378 | One mapping can be used in multiple fields. Name of the field can be used for naming file 379 | 380 | ``` yaml 381 | # app/config/config.yml 382 | iphp_file_store: 383 | mappings: 384 | some_entity: 385 | namer: 386 | translit: ~ 387 | propertyPostfix: 388 | params: { use_field_name : true } 389 | ``` 390 | 391 | #### Replacing strings 392 | 393 | Params of replace namer are key-value pairs with search and replace strings 394 | ``` yaml 395 | # app/config/config.yml 396 | iphp_file_store: 397 | mappings: 398 | some_entity: 399 | namer: 400 | translit: ~ 401 | propertyPostfix: 402 | params: { use_field_name : true } 403 | replace: 404 | params: { File : ~ } 405 | ``` 406 | 407 | 408 | ### Directory Namers 409 | 410 | #### Create subdirectory by date 411 | 412 | For example: Uploaded file 123.jpg, entity createdAt field value 2013-01-01 - path to file will be 2013/01/123.jpg. 413 | Depth options - year, month, date. 414 | 415 | ``` yaml 416 | # app/config/config.yml 417 | iphp_file_store: 418 | mappings: 419 | some_entity: 420 | directory_namer: 421 | date: 422 | params: { field : createdAt, depth : month } 423 | ``` 424 | 425 | #### Using entity field value 426 | 427 | ``` yaml 428 | # app/config/config.yml 429 | iphp_file_store: 430 | mappings: 431 | some_entity: 432 | directory_namer: 433 | property: 434 | params: { field : "id"} 435 | ``` 436 | 437 | #### Using entity field name 438 | 439 | ``` yaml 440 | # app/config/config.yml 441 | iphp_file_store: 442 | mappings: 443 | some_entity: 444 | directory_namer: 445 | property: 446 | params: { use_field_name : true } 447 | ``` 448 | 449 | #### Using entity class name 450 | ``` yaml 451 | # app/config/config.yml 452 | iphp_file_store: 453 | mappings: 454 | some_entity: 455 | directory_namer: 456 | entityName: ~ 457 | ``` 458 | 459 | #### Using chain of directory namers 460 | 461 | Using entity class name and entity field name 462 | 463 | ``` yaml 464 | # app/config/config.yml 465 | iphp_file_store: 466 | mappings: 467 | some_entity: 468 | directory_namer: 469 | entityName: ~ 470 | property: 471 | params: { use_field_name : true } 472 | ``` 473 | -------------------------------------------------------------------------------- /Resources/config/routing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | IphpFilestoreBundle:Default:index 9 | 10 | 11 | -------------------------------------------------------------------------------- /Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | Iphp\FileStoreBundle\Naming\DefaultNamer 10 | Iphp\FileStoreBundle\Naming\DefaultDirectoryNamer 11 | Iphp\FileStoreBundle\Naming\NamerServiceInvoker 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | %iphp.filestore.mappings% 45 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Resources/translations/messages.fr.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Original file name 7 | Nom du fichier 8 | 9 | 10 | 11 | Delete 12 | Supprimer 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Resources/translations/messages.ru.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Original file name 7 | Исходное название файла 8 | 9 | 10 | 11 | Delete 12 | Удалить 13 | 14 | 15 | 16 | Upload File 17 | Загрузить файл 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Resources/views/Form/fields-base.html.twig: -------------------------------------------------------------------------------- 1 | {% block iphp_file_widget %} 2 | 3 | 4 |
5 | 6 | {% if form.file is defined %} 7 | {{ form_widget(form.file) }} 8 | {% endif %} 9 | 10 | {# if was upload error value is uploadedFile and originalName no exists#} 11 | {% if file_data and file_data.originalName is defined %} 12 | {% set fileUrl = file_data.path %} 13 | 14 | {% if show_preview and file_data.width is defined %} 15 | {% block iphp_file_widget_image_preview %} 16 | 17 |
18 | 21 |
22 | 23 |
{{ file_data.width ~ 'x' ~ file_data.height }}
24 | {% endblock iphp_file_widget_image_preview %} 25 | {% endif %} 26 | 27 | 28 | 29 | {% block iphp_file_widget_file_info %} 30 |
31 | 32 | {% block iphp_file_widget_file_link %} 33 |
34 | {{ file_data.fileName }} 35 |
36 | {% endblock iphp_file_widget_file_link %} 37 | 38 | {% block iphp_file_widget_file_attrs %} 39 |
{{ file_data.size /1000 }} Kb
40 | 41 | {% if file_data.originalName != file_data.fileName %} 42 |
43 | {% trans %}Original file name{% endtrans %}: 44 | {{ file_data.originalName }} 45 |
46 | {% endif %} 47 | {% endblock iphp_file_widget_file_attrs %} 48 | 49 | 50 | {% if form.delete is defined %} 51 | {% block iphp_file_widget_file_delete %} 52 |
53 | {{ form_row (form.delete, {'label_attr' : { 'style' :'width:auto;padding-right:10px' }}) }} 54 |
55 | {% endblock iphp_file_widget_file_delete %} 56 | {% endif %} 57 | 58 |
59 | {% endblock iphp_file_widget_file_info %} 60 | 61 | 62 | 63 | 64 | {% endif %} 65 | 66 | 67 | {% block iphp_file_widget_end %} 68 |
69 | {% endblock iphp_file_widget_end %} 70 | 71 |
72 | 73 | 74 | {% endblock iphp_file_widget %} -------------------------------------------------------------------------------- /Resources/views/Form/fields.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'IphpFileStoreBundle:Form:fields-base.html.twig' %} -------------------------------------------------------------------------------- /Tests/ChildOfDummyEntity.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ChildOfDummyEntity extends DummyEntity 11 | { 12 | 13 | } -------------------------------------------------------------------------------- /Tests/DataStorage/Orm/OrmDataStorageTest.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class OrmDataStorageTest extends \PHPUnit_Framework_TestCase 15 | { 16 | /** 17 | * Test the getObjectFromArgs method. 18 | */ 19 | public function testGetObjectFromArgs() 20 | { 21 | if (!class_exists('Doctrine\ORM\Event\LifecycleEventArgs')) { 22 | $this->markTestSkipped('Doctrine\ORM\Event\LifecycleEventArgs does not exist.'); 23 | } else { 24 | $entity = $this->getMock('Iphp\FileStoreBundle\Tests\DummyEntity'); 25 | 26 | $args = $this->getMockBuilder('Doctrine\ORM\Event\LifecycleEventArgs') 27 | ->disableOriginalConstructor() 28 | ->getMock(); 29 | $args 30 | ->expects($this->once()) 31 | ->method('getEntity') 32 | ->will($this->returnValue($entity)); 33 | 34 | $storage = new OrmDataStorage(); 35 | 36 | $this->assertEquals($entity, $storage->getObjectFromArgs($args)); 37 | } 38 | } 39 | 40 | /** 41 | * Tests the getReflectionClass method. 42 | */ 43 | public function testGetReflectionClass() 44 | { 45 | if (!interface_exists('Doctrine\ORM\Proxy\Proxy')) { 46 | $this->markTestSkipped('Doctrine\ORM\Proxy\Proxy does not exist.'); 47 | } else { 48 | $obj = new DummyEntity(); 49 | $adapter = new OrmDataStorage(); 50 | $class = $adapter->getReflectionClass($obj); 51 | 52 | $this->assertEquals($class->getName(), get_class($obj)); 53 | } 54 | } 55 | 56 | /** 57 | * Tests the getReflectionClass method with a proxy. 58 | */ 59 | public function testGetReflectionClassProxy() 60 | { 61 | if (!interface_exists('Doctrine\ORM\Proxy\Proxy')) { 62 | $this->markTestSkipped('Doctrine\ORM\Proxy\Proxy does not exist.'); 63 | } else { 64 | $obj = new DummyEntityProxyORM(); 65 | $adapter = new OrmDataStorage(); 66 | $class = $adapter->getReflectionClass($obj); 67 | 68 | $this->assertEquals($class->getName(), get_parent_class($obj)); 69 | } 70 | } 71 | 72 | 73 | public function testRecomputeChangeSet() 74 | { 75 | 76 | 77 | if (!class_exists('Doctrine\ORM\Event\LifecycleEventArgs')) { 78 | $this->markTestSkipped('Doctrine\ORM\Event\LifecycleEventArgs does not exist.'); 79 | } else { 80 | $entity = $this->getMock('Iphp\FileStoreBundle\Tests\DummyEntity'); 81 | 82 | $args = $this->getMockBuilder('Doctrine\ORM\Event\LifecycleEventArgs') 83 | ->disableOriginalConstructor() 84 | ->getMock(); 85 | $args 86 | ->expects($this->once()) 87 | ->method('getEntity') 88 | ->will($this->returnValue($entity)); 89 | 90 | 91 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') 92 | ->disableOriginalConstructor() 93 | ->getMock(); 94 | 95 | 96 | $args->expects($this->once()) 97 | ->method('getEntityManager') 98 | ->will($this->returnValue($em)); 99 | 100 | 101 | $uow = $this->getMockBuilder('Doctrine\ORM\UnitOfWork') 102 | ->disableOriginalConstructor() 103 | ->getMock(); 104 | 105 | 106 | $em->expects($this->once()) 107 | ->method('getUnitOfWork') 108 | ->will($this->returnValue($uow)); 109 | 110 | 111 | $metadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata') 112 | ->disableOriginalConstructor() 113 | ->getMock(); 114 | 115 | 116 | $em->expects($this->once()) 117 | ->method('getClassMetadata') 118 | ->with(get_class($entity)) 119 | ->will($this->returnValue($metadata)); 120 | 121 | 122 | $storage = new OrmDataStorage(); 123 | $storage->recomputeChangeSet($args); 124 | 125 | } 126 | 127 | } 128 | 129 | 130 | function testPostFlush() 131 | { 132 | if (!class_exists('Doctrine\ORM\Event\LifecycleEventArgs')) { 133 | $this->markTestSkipped('Doctrine\ORM\Event\LifecycleEventArgs does not exist.'); 134 | } else { 135 | $entity = $this->getMock('Iphp\FileStoreBundle\Tests\DummyEntity'); 136 | 137 | $args = $this->getMockBuilder('Doctrine\ORM\Event\LifecycleEventArgs') 138 | ->disableOriginalConstructor() 139 | ->getMock(); 140 | 141 | 142 | $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') 143 | ->disableOriginalConstructor() 144 | ->getMock(); 145 | 146 | 147 | $args->expects($this->any()) 148 | ->method('getEntityManager') 149 | ->will($this->returnValue($em)); 150 | 151 | 152 | $em->expects($this->once())->method('persist')->with($entity); 153 | $em->expects($this->once())->method('flush'); 154 | 155 | 156 | $storage = new OrmDataStorage(); 157 | $storage->postFlush($entity, $args); 158 | } 159 | } 160 | 161 | 162 | public function testCurrentFieldData() 163 | { 164 | 165 | if (!class_exists('Doctrine\ORM\Event\LifecycleEventArgs')) { 166 | $this->markTestSkipped('Doctrine\ORM\Event\PreUpdateEventArgs does not exist.'); 167 | } else { 168 | $entity = $this->getMock('Iphp\FileStoreBundle\Tests\DummyEntity'); 169 | 170 | $args = $this->getMockBuilder('Doctrine\ORM\Event\PreUpdateEventArgs') 171 | ->disableOriginalConstructor() 172 | ->getMock(); 173 | 174 | 175 | $args->expects($this->once()) 176 | ->method('hasChangedField') 177 | ->with('file') 178 | ->will($this->returnValue(true)); 179 | 180 | 181 | $args->expects($this->once()) 182 | ->method('getOldValue') 183 | ->with('file') 184 | ->will($this->returnValue(array(1))); 185 | 186 | $storage = new OrmDataStorage(); 187 | $this->assertSame($storage->previusFieldDataIfChanged('file', $args), array(1)); 188 | } 189 | 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /Tests/Driver/AnnotationDriverTest.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class AnnotationDriverTest extends \PHPUnit_Framework_TestCase 19 | { 20 | /** 21 | * Test that the driver can correctly read the Uploadable 22 | * annotation. 23 | */ 24 | public function testReadUploadableAnnotation() 25 | { 26 | $uploadable = Mocks::getUploadableMock($this); 27 | 28 | 29 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 30 | $reader 31 | ->expects($this->once()) 32 | ->method('getClassAnnotation') 33 | ->will($this->returnValue($uploadable)); 34 | 35 | $entity = new DummyEntity(); 36 | $driver = new AnnotationDriver($reader); 37 | $annot = $driver->readUploadable(new \ReflectionClass($entity)); 38 | 39 | $this->assertEquals($uploadable, $annot); 40 | } 41 | 42 | 43 | public function testReadUploadableAnnotationFromParent() 44 | { 45 | $uploadable = Mocks::getUploadableMock($this); 46 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 47 | 48 | 49 | $reader 50 | ->expects($this->any()) 51 | ->method('getClassAnnotation') 52 | ->will($this->returnCallBack ( function() use ( $uploadable) { 53 | 54 | $args = func_get_args(); 55 | 56 | if ('Iphp\\FileStoreBundle\\Tests\\ChildOfDummyEntity' === $args[0]->getName()) return null; 57 | if ('Iphp\\FileStoreBundle\\Tests\\DummyEntity' === $args[0]->getName()) return $uploadable; 58 | 59 | })); 60 | 61 | $entity = new ChildOfDummyEntity(); 62 | $driver = new AnnotationDriver($reader); 63 | 64 | $annot = $driver->readUploadable(new \ReflectionClass($entity)); 65 | 66 | $this->assertEquals($uploadable, $annot); 67 | 68 | } 69 | 70 | 71 | 72 | 73 | 74 | 75 | /** 76 | * Tests that the driver returns null when no Uploadable annotation 77 | * is found. 78 | */ 79 | public function testReadUploadableAnnotationReturnsNullWhenNonePresent() 80 | { 81 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 82 | $reader 83 | ->expects($this->once()) 84 | ->method('getClassAnnotation') 85 | ->will($this->returnValue(null)); 86 | 87 | $entity = new DummyEntity(); 88 | $driver = new AnnotationDriver($reader); 89 | $annot = $driver->readUploadable(new \ReflectionClass($entity)); 90 | 91 | $this->assertEquals(null, $annot); 92 | } 93 | 94 | /** 95 | * Tests that the driver correctly reads one UploadableField 96 | * property. 97 | */ 98 | public function testReadOneUploadableField() 99 | { 100 | $uploadableField = Mocks::getUploadableFieldMock($this); 101 | 102 | $uploadableField 103 | ->expects($this->once()) 104 | ->method('setFileUploadPropertyName'); 105 | 106 | $entity = new DummyEntity(); 107 | $class = new \ReflectionClass($entity); 108 | 109 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 110 | $reader 111 | ->expects($this->any()) 112 | ->method('getPropertyAnnotation') 113 | ->will($this->returnCallback(function() use ($class, $uploadableField) { 114 | $args = func_get_args(); 115 | 116 | if ( $args[0]->class === $class->getName() && 'file' === $args[0]->getName()) { 117 | return $uploadableField; 118 | } 119 | 120 | return null; 121 | })); 122 | 123 | $driver = new AnnotationDriver($reader); 124 | $fields = $driver->readUploadableFields($class); 125 | 126 | $this->assertEquals(1, count($fields)); 127 | } 128 | 129 | 130 | 131 | 132 | public function testReadOneUploadableFieldFromParent() 133 | { 134 | $uploadableField = Mocks::getUploadableFieldMock($this); 135 | $uploadableField 136 | ->expects($this->once()) 137 | ->method('setFileUploadPropertyName') 138 | ->with ('file'); 139 | 140 | 141 | $entity = new ChildOfDummyEntity(); 142 | $class = new \ReflectionClass($entity); 143 | 144 | 145 | 146 | 147 | 148 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 149 | $reader 150 | ->expects($this->any()) 151 | ->method('getPropertyAnnotation') 152 | ->will($this->returnCallback(function() use ( $entity , $uploadableField) { 153 | $args = func_get_args(); 154 | if (get_parent_class($entity) === $args[0]->class && 'file' === $args[0]->getName()) { 155 | return $uploadableField; 156 | } 157 | 158 | return null; 159 | })); 160 | 161 | 162 | $driver = new AnnotationDriver($reader); 163 | $fields = $driver->readUploadableFields($class); 164 | 165 | $this->assertEquals(1, count($fields)); 166 | } 167 | 168 | 169 | 170 | /** 171 | * Tests that the driver correctly reads one UploadableField 172 | * property. 173 | */ 174 | public function testReadUploadableFieldSingle() 175 | { 176 | $uploadableField = Mocks::getUploadableFieldMock($this); 177 | $uploadableField->expects($this->once())->method('setFileUploadPropertyName'); 178 | 179 | $entity = new DummyEntity(); 180 | $class = new \ReflectionClass($entity); 181 | 182 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 183 | $reader 184 | ->expects($this->any()) 185 | ->method('getPropertyAnnotation') 186 | ->will($this->returnCallback(function() use ($uploadableField) { 187 | $args = func_get_args(); 188 | if ('file' === $args[0]->getName()) { 189 | return $uploadableField; 190 | } 191 | 192 | return null; 193 | })); 194 | 195 | $driver = new AnnotationDriver($reader); 196 | $this->assertEquals ($driver->readUploadableField($class, 'file'), $uploadableField); 197 | 198 | 199 | } 200 | 201 | 202 | /** 203 | * Tests that the driver correctly reads one UploadableField 204 | * property. 205 | */ 206 | public function testReadUploadableFieldNoMapping() 207 | { 208 | $uploadableField = Mocks::getUploadableFieldMock($this); 209 | $uploadableField->expects($this->never())->method('setPropertyName'); 210 | 211 | $entity = new DummyEntity(); 212 | $class = new \ReflectionClass($entity); 213 | 214 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 215 | $reader 216 | ->expects($this->any()) 217 | ->method('getPropertyAnnotation') 218 | ->will($this->returnCallback(function() use ($uploadableField) { 219 | $args = func_get_args(); 220 | if ('file' === $args[0]->getName()) { 221 | return null; 222 | } 223 | return null; 224 | })); 225 | 226 | $driver = new AnnotationDriver($reader); 227 | $this->assertEquals ($driver->readUploadableField($class, 'file'), null); 228 | } 229 | 230 | 231 | 232 | 233 | 234 | /** 235 | * Test that the driver correctly reads two UploadableField 236 | * properties. 237 | */ 238 | public function testReadTwoUploadableFields() 239 | { 240 | $fileField = Mocks::getUploadableFieldMock($this); 241 | $fileField->expects($this->once())->method('setFileUploadPropertyName'); 242 | 243 | $imageField = Mocks::getUploadableFieldMock($this); 244 | $imageField->expects($this->once())->method('setFileUploadPropertyName'); 245 | 246 | $entity = new TwoFieldsDummyEntity(); 247 | $class = new \ReflectionClass($entity); 248 | 249 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 250 | $reader 251 | ->expects($this->any()) 252 | ->method('getPropertyAnnotation') 253 | ->will($this->returnCallback(function() use ($fileField, $imageField) { 254 | $args = func_get_args(); 255 | if ('file' === $args[0]->getName()) { 256 | return $fileField; 257 | } elseif ('image' === $args[0]->getName()) { 258 | return $imageField; 259 | } 260 | 261 | return null; 262 | })); 263 | 264 | $driver = new AnnotationDriver($reader); 265 | $fields = $driver->readUploadableFields($class); 266 | 267 | $this->assertEquals(2, count($fields)); 268 | } 269 | 270 | /** 271 | * Test that the driver reads zero UploadableField 272 | * properties when none exist. 273 | */ 274 | public function testReadNoUploadableFieldsWhenNoneExist() 275 | { 276 | $entity = new DummyEntity(); 277 | $class = new \ReflectionClass($entity); 278 | 279 | $reader = $this->getMock('Doctrine\Common\Annotations\Reader'); 280 | $reader 281 | ->expects($this->any()) 282 | ->method('getPropertyAnnotation') 283 | ->will($this->returnValue(null)); 284 | 285 | $driver = new AnnotationDriver($reader); 286 | $fields = $driver->readUploadableFields($class); 287 | 288 | $this->assertEquals(0, count($fields)); 289 | } 290 | 291 | } 292 | -------------------------------------------------------------------------------- /Tests/DummyEntity.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class DummyEntity 13 | { 14 | 15 | protected $id; 16 | 17 | /** 18 | * @FileStore\UploadableField(mapping="dummy_file") 19 | */ 20 | protected $file; 21 | 22 | 23 | protected $title; 24 | 25 | protected $createdAt; 26 | 27 | /** 28 | * @var array Default iphpfilestore bundle configuration for DummyEntity 29 | */ 30 | public $defaultFileStoreConfig= array( 31 | 'dummy_file' => array( 32 | 'upload_dir' => '/www/web/images', 33 | 'upload_path' => '/images', 34 | 'namer' => array('translit' => array('service' => 'iphp.filestore.namer.default')) 35 | ) 36 | ); 37 | 38 | 39 | public function setId($id) 40 | { 41 | $this->id = $id; 42 | return $this; 43 | } 44 | 45 | public function getId() 46 | { 47 | return $this->id; 48 | } 49 | 50 | 51 | public function getFile() 52 | { 53 | return $this->file; 54 | } 55 | 56 | public function setFile($file) 57 | { 58 | $this->file = $file; 59 | } 60 | 61 | public function setTitle($title) 62 | { 63 | $this->title = $title; 64 | return $this; 65 | } 66 | 67 | public function getTitle() 68 | { 69 | return $this->title; 70 | } 71 | 72 | public function setCreatedAt($createdAt) 73 | { 74 | $this->createdAt = $createdAt; 75 | return $this; 76 | } 77 | 78 | public function getCreatedAt() 79 | { 80 | return $this->createdAt; 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Tests/DummyEntityProxyORM.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class DummyEntityProxyORM extends DummyEntity implements Proxy 14 | { 15 | public function __load() { } 16 | 17 | public function __isInitialized() { } 18 | 19 | /** 20 | * Sets the callback to be used when cloning the proxy. That initializer should accept 21 | * a single parameter, which is the cloned proxy instance itself. 22 | * 23 | * @param Closure|null $cloner 24 | * 25 | * @return void 26 | */ 27 | public function __setCloner(Closure $cloner = null) 28 | { 29 | // TODO: Implement __setCloner() method. 30 | } 31 | 32 | /** 33 | * Retrieves the callback to be used when cloning the proxy. 34 | * 35 | * @see __setCloner 36 | * 37 | * @return Closure|null 38 | */ 39 | public function __getCloner() 40 | { 41 | // TODO: Implement __getCloner() method. 42 | } 43 | 44 | /** 45 | * Marks the proxy as initialized or not. 46 | * 47 | * @param boolean $initialized 48 | * 49 | * @return void 50 | */ 51 | public function __setInitialized($initialized) 52 | { 53 | // TODO: Implement __setInitialized() method. 54 | } 55 | 56 | /** 57 | * Retrieves the list of lazy loaded properties for a given proxy 58 | * 59 | * @return array Keys are the property names, and values are the default values 60 | * for those properties. 61 | */ 62 | public function __getLazyProperties() 63 | { 64 | // TODO: Implement __getLazyProperties() method. 65 | } 66 | 67 | /** 68 | * Retrieves the initializer callback used to initialize the proxy. 69 | * 70 | * @see __setInitializer 71 | * 72 | * @return Closure|null 73 | */ 74 | public function __getInitializer() 75 | { 76 | // TODO: Implement __getInitializer() method. 77 | } 78 | 79 | /** 80 | * Sets the initializer callback to be used when initializing the proxy. That 81 | * initializer should accept 3 parameters: $proxy, $method and $params. Those 82 | * are respectively the proxy object that is being initialized, the method name 83 | * that triggered initialization and the parameters passed to that method. 84 | * 85 | * @param Closure|null $initializer 86 | * 87 | * @return void 88 | */ 89 | public function __setInitializer(Closure $initializer = null) 90 | { 91 | // TODO: Implement __setInitializer() method. 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Tests/DummyEntitySeparateDataField.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class DummyEntitySeparateDataField 13 | { 14 | /** 15 | * @FileStore\UploadableField(mapping="dummy_file", ) 16 | */ 17 | protected $file; 18 | 19 | 20 | 21 | protected $file_data; 22 | 23 | /** 24 | * @var array Default iphpfilestore bundle configuration for DummyEntity 25 | */ 26 | public $defaultFileStoreConfig= array( 27 | 'dummy_file' => array( 28 | 'upload_dir' => '/www/web/images', 29 | 'upload_path' => '/images', 30 | 'namer' => array('translit' => array('service' => 'iphp.filestore.namer.default')) 31 | ) 32 | ); 33 | 34 | public function getFile() 35 | { 36 | $this->file; 37 | } 38 | 39 | public function setFile($file) 40 | { 41 | $this->file = $file; 42 | } 43 | 44 | public function setFileData($file_data) 45 | { 46 | $this->file_data = $file_data; 47 | return $this; 48 | } 49 | 50 | public function getFileData() 51 | { 52 | return $this->file_data; 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Tests/File/FileTest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class FileTest extends \PHPUnit_Framework_TestCase 14 | { 15 | 16 | 17 | 18 | 19 | function testCreateEmpty() 20 | { 21 | $file = File::createEmpty(); 22 | 23 | $this->assertTrue ($file instanceof File); 24 | $this->assertSame ($file->isDeleted(), false); 25 | $this->assertSame ($file->getPath(), ''); 26 | $this->assertSame ($file->isValid(), true); 27 | } 28 | 29 | 30 | 31 | function testDelete() 32 | { 33 | $file = new File(); 34 | $this->assertSame ($file->isDeleted(), false); 35 | $file->delete(); 36 | $this->assertSame ($file->isDeleted(), true); 37 | } 38 | } -------------------------------------------------------------------------------- /Tests/FileStorage/123.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitiko/IphpFileStoreBundle/fd6fd1658f4524f9ca0a05e191cbc5648fcbc935/Tests/FileStorage/123.jpg -------------------------------------------------------------------------------- /Tests/FileStorage/FileSystemStorageTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class FileSystemStorageTest extends \PHPUnit_Framework_TestCase 20 | { 21 | 22 | /** 23 | * @var \Iphp\FileStoreBundle\FileStorage\FileSystemStorage; 24 | */ 25 | protected $storage; 26 | 27 | 28 | protected $uploadedImageFile; 29 | 30 | protected $targetImageFileExistingDir; 31 | 32 | 33 | protected $targetImageFileNewDir; 34 | 35 | 36 | protected $targetImageFileExistingReadonlyDir; 37 | 38 | /** 39 | * Sets up the test. 40 | */ 41 | public function setUp() 42 | { 43 | $this->storage = new FileSystemStorage(); 44 | 45 | vfsStreamWrapper::register(); 46 | vfsStreamWrapper::setRoot(new vfsStreamDirectory('site_root')); 47 | vfsStream::setup('site_root', 0700, array( 48 | 49 | 'uploaded' => array('123.jpg' => file_get_contents(__DIR__ . '/123.jpg')), 50 | 'web' => array( 51 | 'images' => array(), 52 | // 'images_readonly' => array() 53 | ) 54 | 55 | )); 56 | 57 | if (version_compare(PHP_VERSION, '5.4.0') >= 0) 58 | { 59 | mkdir (vfsStream::url('site_root/web/images-readonly'),0700); 60 | chown (vfsStream::url('site_root/web/images-readonly'), vfsStream::GROUP_USER_1); 61 | } 62 | 63 | $this->uploadedImageFile = vfsStream::url('site_root/uploaded/123.jpg'); 64 | $this->targetImageFileExistingDir = vfsStream::url('site_root/web/images/123.jpg'); 65 | $this->targetImageFileExistingReadonlyDir = vfsStream::url('site_root/web/images-readonly/123.jpg'); 66 | $this->targetImageFileNewDir = vfsStream::url('site_root/web/images/new/123.jpg'); 67 | } 68 | 69 | 70 | protected function createPropertyMapping($resolveFrom, $resolveTo, $targetData = array()) 71 | { 72 | $propertyMapping = Mocks::getPropertyMappingMock($this); 73 | $propertyMapping->expects($this->any()) 74 | ->method('resolveFileName') 75 | ->with($resolveFrom) 76 | ->will($this->returnValue($resolveTo)); 77 | 78 | 79 | if ($targetData) $propertyMapping->expects($this->any()) 80 | ->method('prepareFileName') 81 | ->with($resolveFrom, $this->storage) 82 | ->will($this->returnValue($targetData)); 83 | 84 | return $propertyMapping; 85 | } 86 | 87 | 88 | public function testFileExists() 89 | { 90 | 91 | $this->assertTrue($this->storage->fileExists( $this->uploadedImageFile)); 92 | 93 | } 94 | 95 | 96 | public function testFileNoExists() 97 | { 98 | $this->assertFalse($this->storage->fileExists($this->targetImageFileExistingDir)); 99 | } 100 | 101 | 102 | public function testRemoveExistingFile() 103 | { 104 | $this->assertTrue($this->storage->removeFile($this->uploadedImageFile)); 105 | } 106 | 107 | 108 | public function testRemoveNonExistingFile() 109 | { 110 | 111 | $this->assertNull($this->storage->removeFile($this->targetImageFileExistingDir)); 112 | } 113 | 114 | 115 | public function testUploadImageFileToExistingDir() 116 | { 117 | //where file will be copied 118 | $propertyMapping = $this->createPropertyMapping('123.jpg', $this->targetImageFileExistingDir, 119 | array('123.jpg', '/images/123.jpg')); 120 | 121 | $file = new File( 122 | $this->uploadedImageFile, '123.jpg', 'image/jpeg', null, null, true); 123 | 124 | 125 | $this->storage->setSameFileChecker(function () 126 | { 127 | return false; 128 | }); 129 | 130 | $this->assertFileExists($this->uploadedImageFile); 131 | $this->assertFileNotExists($this->targetImageFileExistingDir); 132 | 133 | 134 | $fileData = $this->storage->upload($propertyMapping, $file); 135 | 136 | 137 | $this->assertFileExists($this->uploadedImageFile); 138 | $this->assertFileExists($this->targetImageFileExistingDir); 139 | 140 | } 141 | 142 | 143 | public function testUploadUploadedImageFileToExistingDir() 144 | { 145 | //test mode 146 | $uploadedFile = new \Symfony\Component\HttpFoundation\File\UploadedFile( 147 | $this->uploadedImageFile, '123.jpg', 'image/jpeg', null, null, true); 148 | 149 | $propertyMapping = $this->createPropertyMapping('123.jpg', $this->targetImageFileExistingDir, 150 | array('123.jpg', '/images/123.jpg')); 151 | 152 | $this->storage->setSameFileChecker(function () 153 | { 154 | return false; 155 | }); 156 | 157 | 158 | $this->assertFileExists($this->uploadedImageFile); 159 | $this->assertFileNotExists($this->targetImageFileExistingDir); 160 | $this->assertFileExists(dirname($this->targetImageFileExistingDir)); 161 | 162 | $filesize = filesize($this->uploadedImageFile); 163 | $fileData = $this->storage->upload($propertyMapping, $uploadedFile); 164 | $this->assertFileExists($this->targetImageFileExistingDir); 165 | $this->assertFileNotExists($this->uploadedImageFile); 166 | $this->assertTrue(filesize($this->targetImageFileExistingDir) == $filesize); 167 | 168 | 169 | $testFileData = array 170 | ( 171 | 'fileName' => '123.jpg', 172 | 'originalName' => '123.jpg', 173 | 'mimeType' => 'image/jpeg', 174 | 'size' => $filesize, 175 | 'path' => '/images/123.jpg' 176 | ); 177 | 178 | if (function_exists('getimagesize')) { 179 | $testFileData['width'] = 660; 180 | $testFileData['height'] = 498; 181 | } 182 | 183 | $this->assertSame($fileData, $testFileData); 184 | 185 | 186 | } 187 | 188 | 189 | public function testUploadImageFileToNewDir() 190 | { 191 | //where file will be copied 192 | $propertyMapping = $this->createPropertyMapping('123.jpg', $this->targetImageFileNewDir, 193 | array('123.jpg', '/images/new/123.jpg')); 194 | $file = new File($this->uploadedImageFile, '123.jpg', 'image/jpeg', null, null, true); 195 | 196 | $this->storage->setSameFileChecker(function () 197 | { 198 | return false; 199 | }); 200 | 201 | 202 | $this->assertFileNotExists($this->targetImageFileNewDir); 203 | $this->assertFileExists($this->uploadedImageFile); 204 | 205 | $this->assertFileNotExists(dirname($this->targetImageFileNewDir)); 206 | $filesize = filesize($this->uploadedImageFile); 207 | 208 | $fileData = $this->storage->upload($propertyMapping, $file); 209 | 210 | $this->assertFileExists(dirname($this->targetImageFileNewDir)); 211 | $this->assertFileExists($this->targetImageFileNewDir); 212 | $this->assertFileExists($this->uploadedImageFile); 213 | 214 | $testFileData = array 215 | ( 216 | 'fileName' => '123.jpg', 217 | 'originalName' => '123.jpg', 218 | 'mimeType' => 'image/jpeg', 219 | 'size' => $filesize, 220 | 'path' => '/images/new/123.jpg' 221 | ); 222 | 223 | if (function_exists('getimagesize')) { 224 | $testFileData['width'] = 660; 225 | $testFileData['height'] = 498; 226 | } 227 | 228 | $this->assertSame($fileData, $testFileData); 229 | 230 | } 231 | 232 | 233 | 234 | 235 | 236 | /** 237 | * Test that an exception is thrown when try to move file to readonly dir 238 | * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException 239 | */ 240 | public function testUploadUploadedImageFileToExistingReadonlyDir() 241 | { 242 | 243 | if (version_compare(PHP_VERSION, '5.4.0','<')) 244 | { 245 | $this->markTestSkipped('vfsStream and chown() works only in PHP 5.4+'); 246 | return; 247 | } 248 | 249 | 250 | //test mode 251 | $uploadedFile = new \Symfony\Component\HttpFoundation\File\UploadedFile( 252 | $this->uploadedImageFile, '123.jpg', 'image/jpeg', null, null, true); 253 | 254 | $propertyMapping = $this->createPropertyMapping('123.jpg', $this->targetImageFileExistingReadonlyDir , 255 | array('123.jpg', '/images-readonly/123.jpg')); 256 | 257 | $this->storage->setSameFileChecker(function () 258 | { 259 | return false; 260 | }); 261 | 262 | $fileData = $this->storage->upload($propertyMapping, $uploadedFile); 263 | } 264 | 265 | 266 | /** 267 | * Test that an exception is thrown when try to move file to readonly dir 268 | * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException 269 | */ 270 | public function testUploadImageFileToExistingReadonlyDir() 271 | { 272 | 273 | if (version_compare(PHP_VERSION, '5.4.0','<')) 274 | { 275 | $this->markTestSkipped('vfsStream and chown() works only in PHP 5.4+'); 276 | return; 277 | } 278 | 279 | 280 | //test mode 281 | $uploadedFile = new File( 282 | $this->uploadedImageFile, '123.jpg', 'image/jpeg', null, null, true); 283 | 284 | $propertyMapping = $this->createPropertyMapping('123.jpg', $this->targetImageFileExistingReadonlyDir , 285 | array('123.jpg', '/images-readonly/123.jpg')); 286 | 287 | $this->storage->setSameFileChecker(function () 288 | { 289 | return false; 290 | }); 291 | 292 | $fileData = $this->storage->upload($propertyMapping, $uploadedFile); 293 | } 294 | 295 | 296 | function testSetWebDir() 297 | { 298 | $this->storage->setWebDir('/srv/www/web'); 299 | $this->assertSame($this->storage->getWebDir(), '/srv/www/web'); 300 | } 301 | 302 | 303 | } 304 | -------------------------------------------------------------------------------- /Tests/Fixtures/files/text.txt: -------------------------------------------------------------------------------- 1 | test file -------------------------------------------------------------------------------- /Tests/Fixtures/images/front-images-list.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitiko/IphpFileStoreBundle/fd6fd1658f4524f9ca0a05e191cbc5648fcbc935/Tests/Fixtures/images/front-images-list.jpeg -------------------------------------------------------------------------------- /Tests/Fixtures/images/github1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitiko/IphpFileStoreBundle/fd6fd1658f4524f9ca0a05e191cbc5648fcbc935/Tests/Fixtures/images/github1.png -------------------------------------------------------------------------------- /Tests/Fixtures/images/php-elephant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitiko/IphpFileStoreBundle/fd6fd1658f4524f9ca0a05e191cbc5648fcbc935/Tests/Fixtures/images/php-elephant.png -------------------------------------------------------------------------------- /Tests/Fixtures/images/sonata-admin-iphpfile.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vitiko/IphpFileStoreBundle/fd6fd1658f4524f9ca0a05e191cbc5648fcbc935/Tests/Fixtures/images/sonata-admin-iphpfile.jpeg -------------------------------------------------------------------------------- /Tests/Form/DataTransformer/FileDataTransformerTest.php: -------------------------------------------------------------------------------- 1 | fileStorage = Mocks::getFileStorageMock($this); 26 | $this->transformer = new FileDataTransformer($this->fileStorage); 27 | } 28 | 29 | 30 | function testTransform() 31 | 32 | { 33 | $this->assertSame($this->transformer->transform(array(1, 2, 3)), array(1, 2, 3)); 34 | } 35 | 36 | 37 | function testReverseTransformDeleteFile() 38 | { 39 | $propertyMapping = Mocks::getPropertyMappingMock($this); 40 | $file = Mocks::getFileMock($this); 41 | $this->transformer->setMapping($propertyMapping, FileDataTransformer::MODE_UPLOAD_FIELD); 42 | 43 | 44 | $propertyMapping->expects($this->once()) 45 | ->method('resolveFileName') 46 | ->with('123.jpg') 47 | ->will($this->returnValue('/path/to/123.jpg')); 48 | 49 | 50 | $this->fileStorage 51 | ->expects($this->once()) 52 | ->method('removeFile') 53 | ->with('/path/to/123.jpg'); 54 | 55 | $this->assertSame( 56 | $this->transformer->reverseTransform(array('delete' => 1, 'file' => $file, 'fileName' => '123.jpg')), 57 | $file); 58 | } 59 | 60 | 61 | function testReverseTransformNoDeleteFile() 62 | { 63 | $propertyMapping = Mocks::getPropertyMappingMock($this); 64 | $file = Mocks::getFileMock($this); 65 | $this->transformer->setMapping($propertyMapping, FileDataTransformer::MODE_UPLOAD_FIELD); 66 | 67 | $this->fileStorage 68 | ->expects($this->never()) 69 | ->method('removeFile'); 70 | 71 | $this->assertSame( 72 | $this->transformer->reverseTransform(array('delete' => 0, 'file' => $file, 'fileName' => '123.jpg')), 73 | $file); 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /Tests/Form/DataTransformer/FileDataViewTransformerTest.php: -------------------------------------------------------------------------------- 1 | transformer = new FileDataViewTransformer(); 22 | } 23 | 24 | 25 | function testTransform() 26 | 27 | { 28 | $this->assertSame($this->transformer->transform(array(1, 2, 3)), array(1, 2, 3)); 29 | } 30 | 31 | 32 | 33 | function testReverseTransform() 34 | 35 | { 36 | $this->assertSame($this->transformer->reverseTransform(array(1, 2, 3)), array(1, 2, 3)); 37 | } 38 | } -------------------------------------------------------------------------------- /Tests/Form/Type/FileTypeBindSubscriberTest.php: -------------------------------------------------------------------------------- 1 | dataStorage = Mocks::getDataStorageMock($this); 52 | $this->driver = Mocks::getAnnotationDriverMock($this); 53 | $this->namerServiceInvoker = Mocks::getNamerServiceInvokerMock($this); 54 | 55 | 56 | $this->propertyMappingFactory = Mocks::getPropertyMappingFactoryMock($this, 57 | $this->namerServiceInvoker, $this->driver); 58 | 59 | 60 | $this->transformer = $this->getMockBuilder('Iphp\FileStoreBundle\Form\DataTransformer\FileDataTransformer') 61 | ->disableOriginalConstructor() 62 | ->getMock(); 63 | 64 | $this->subscriber = new FileTypeBindSubscriber ($this->propertyMappingFactory, $this->dataStorage, $this->transformer); 65 | } 66 | 67 | 68 | function testSubscribedEvents() 69 | { 70 | $this->assertSame(FileTypeBindSubscriber::getSubscribedEvents(), 71 | array(FormEvents::PRE_SUBMIT => 'preBind', 72 | FormEvents::PRE_SET_DATA => 'preSet')); 73 | } 74 | 75 | 76 | function testPreBind() 77 | { 78 | 79 | $obj = new DummyEntity(); 80 | $class = new \ReflectionClass($obj); 81 | 82 | $formEvent = $this->getMockBuilder('Symfony\Component\Form\FormEvent') 83 | ->disableOriginalConstructor() 84 | ->getMock(); 85 | 86 | $form = $this->getMockBuilder('Symfony\Component\Form\Form') 87 | ->disableOriginalConstructor() 88 | ->getMock(); 89 | 90 | $parentForm = $this->getMockBuilder('Symfony\Component\Form\Form') 91 | ->disableOriginalConstructor() 92 | ->getMock(); 93 | 94 | 95 | $formEvent->expects($this->once()) 96 | ->method('getForm') 97 | ->will($this->returnValue($form)); 98 | 99 | 100 | $form->expects($this->once()) 101 | ->method('getParent') 102 | ->will($this->returnValue($parentForm)); 103 | 104 | $parentForm->expects($this->once()) 105 | ->method('getData') 106 | ->will($this->returnValue($obj)); 107 | 108 | 109 | $this->dataStorage->expects($this->once()) 110 | ->method('getReflectionClass') 111 | ->with($obj) 112 | ->will($this->returnValue($class)); 113 | 114 | 115 | $propertyMapping = Mocks::getPropertyMappingMock($this); 116 | 117 | $this->propertyMappingFactory->expects($this->once()) 118 | ->method('getMappingFromField') 119 | ->with($obj, $class, 'file') 120 | ->will($this->returnValue($propertyMapping)); 121 | 122 | 123 | $form->expects($this->once()) 124 | ->method('getName') 125 | ->will($this->returnValue('file')); 126 | 127 | 128 | $this->transformer->expects($this->once()) 129 | ->method('setMapping') 130 | ->with($propertyMapping); 131 | 132 | 133 | $this->subscriber->preBind($formEvent); 134 | } 135 | } -------------------------------------------------------------------------------- /Tests/Functional/AppKernel.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class AppKernel extends Kernel 12 | { 13 | protected $config; 14 | 15 | protected $testEnv; 16 | 17 | public function __construct($config, $testEnv = 'default') 18 | { 19 | //separate generated container 20 | parent::__construct($testEnv . '_' . substr(md5($config), 0, 3), true); 21 | 22 | $fs = new Filesystem(); 23 | if (!$fs->isAbsolutePath($config)) { 24 | $config = __DIR__ . '/config/' . $config; 25 | } 26 | 27 | if (!file_exists($config)) { 28 | throw new \RuntimeException(sprintf('The config file "%s" does not exist.', $config)); 29 | } 30 | 31 | $this->config = $config; 32 | $this->testEnv = $testEnv; 33 | } 34 | 35 | public function registerBundles() 36 | { 37 | return array( 38 | new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), 39 | new \Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), 40 | new \Symfony\Bundle\TwigBundle\TwigBundle(), 41 | 42 | new \Iphp\FileStoreBundle\IphpFileStoreBundle(), 43 | new \Iphp\FileStoreBundle\Tests\Functional\TestBundle\TestBundle(), 44 | new \Iphp\FileStoreBundle\Tests\Functional\TestXmlConfigBundle\TestXmlConfigBundle() 45 | ); 46 | } 47 | 48 | public function registerContainerConfiguration(LoaderInterface $loader) 49 | { 50 | $loader->load($this->config); 51 | } 52 | 53 | public function getCacheDir() 54 | { 55 | return $this->getTestEnvDir() . '/app/cache/' . substr(md5($this->config), 0, 3) . ''; 56 | } 57 | 58 | public function getConfig() 59 | { 60 | return $this->config; 61 | } 62 | 63 | 64 | public static function getTestBaseDir() 65 | { 66 | return sys_get_temp_dir() . '/IphpFileStoreTestBundle'; 67 | } 68 | 69 | 70 | 71 | public function getTestEnvDir() 72 | { 73 | return self::getTestBaseDir().'/'. $this->testEnv; 74 | } 75 | 76 | 77 | public function makeTestEnvDir() 78 | { 79 | $fs = new Filesystem(); 80 | $fs->remove($this->getTestEnvDir()); 81 | $fs->mkdir($this->getTestEnvDir()); 82 | } 83 | 84 | 85 | protected function getKernelParameters() 86 | { 87 | 88 | 89 | return array_merge( 90 | parent::getKernelParameters(), array( 91 | 'kernel.test_env' => $this->testEnv, 92 | 'kernel.test_env_dir' => $this->getTestEnvDir(), 93 | 94 | ) 95 | ); 96 | } 97 | 98 | public function getTestEnv() 99 | { 100 | return $this->testEnv; 101 | } 102 | 103 | 104 | } -------------------------------------------------------------------------------- /Tests/Functional/BaseTestCase.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class BaseTestCase extends WebTestCase 11 | { 12 | protected $testCaseUniqId; 13 | 14 | 15 | static protected function createKernel(array $options = array()) 16 | { 17 | return self::$kernel = new AppKernel( 18 | isset($options['config']) ? $options['config'] : 'default.yml', static::getTestEnvFromCalledClass() 19 | ); 20 | } 21 | 22 | static function getTestEnvFromCalledClass() 23 | { 24 | $class = explode('\\', get_called_class()); 25 | return end($class); 26 | 27 | } 28 | protected function setUp() 29 | { 30 | $dir = AppKernel::getTestBaseDir().'/'.static::getTestEnvFromCalledClass(); 31 | $fs = new Filesystem(); 32 | $fs->remove($dir); 33 | } 34 | 35 | protected function getKernel() 36 | { 37 | return self::$kernel; 38 | } 39 | 40 | protected function getContainer() 41 | { 42 | return $this->getKernel()->getContainer(); 43 | } 44 | 45 | protected function getEntityManager() 46 | { 47 | return $this->getContainer()->get('doctrine.orm.entity_manager'); 48 | } 49 | 50 | 51 | protected final function importDatabaseSchema() 52 | { 53 | 54 | $em = $this->getEntityManager(); 55 | $metadata = $em->getMetadataFactory()->getAllMetadata(); 56 | if (!empty($metadata)) { 57 | $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($em); 58 | $schemaTool->dropDatabase(); 59 | $schemaTool->createSchema($metadata); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Functional/FileSaveTest.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | use Iphp\FileStoreBundle\Tests\Functional\TestXmlConfigBundle\Entity\File; 10 | use Symfony\Component\Console\Tester\CommandTester; 11 | use Iphp\FileStoreBundle\Command\RepairFileDataCommand; 12 | use Symfony\Bundle\FrameworkBundle\Console\Application; 13 | 14 | class FileSaveTest extends BaseTestCase 15 | { 16 | 17 | 18 | /** 19 | * test saving entity with file property from parent abstract uploadable class 20 | */ 21 | public function testFileSaveUpload() 22 | { 23 | $client = $this->createClient(); 24 | $this->importDatabaseSchema(); 25 | 26 | 27 | $file = new File(); 28 | 29 | $existsFile = new \Symfony\Component\HttpFoundation\File\File( 30 | __DIR__ . '/../Fixtures/files/text.txt'); 31 | 32 | $file->setTitle('new file') 33 | ->setDate(new \DateTime('2013-04-04')) 34 | ->setFile($existsFile); 35 | 36 | 37 | $this->getEntityManager()->persist($file); 38 | $this->getEntityManager()->flush(); 39 | 40 | 41 | 42 | $this->assertSame($file->getFile(), array( 43 | 'fileName' => '/File/file/2013/1.txt', 44 | 'originalName' => 'text.txt', 45 | 'mimeType' => 'text/plain', 46 | 'size' => 9, 47 | 'path' => '/file/File/file/2013/1.txt', 48 | )); 49 | 50 | unset($file); 51 | $this->getEntityManager()->clear(); 52 | $this->getKernel()->shutdown(); 53 | 54 | 55 | 56 | 57 | $client = $this->createClient(array('config' => 'default_newfilepath.yml')); 58 | $application = new Application($client->getKernel()); 59 | $application->add(new RepairFileDataCommand()); 60 | 61 | $command = $application->find('iphp:filestore:repair'); 62 | 63 | 64 | $commandTester = new CommandTester($command); 65 | 66 | 67 | //using web directory setted in config/default.yml 68 | $commandTester->execute(array( 69 | 'command' => $command->getName(), 70 | '--entity' => 'TestXmlConfigBundle:File', 71 | '--field' => 'file', 72 | '--force' => 1, // move file to new location 73 | '--webdir' => realpath($this->getContainer()->getParameter('kernel.test_env_dir') . '/web/') 74 | )); 75 | 76 | 77 | $newFile = $this->getEntityManager()->getRepository('TestXmlConfigBundle:File')->findOneByTitle('new file'); 78 | 79 | 80 | $this->assertSame($newFile->getFile(), array( 81 | 'fileName' => '/1/new-file.txt', 82 | 'originalName' => 'text.txt', 83 | 'mimeType' => 'text/plain', 84 | 'size' => 9, 85 | 'path' => '/other/uploads/1/new-file.txt', 86 | )); 87 | 88 | 89 | unset($newFile); 90 | unset($commandTester); 91 | unset( $command ); 92 | unset($application); 93 | $this->getEntityManager()->clear(); 94 | $this->getKernel()->shutdown(); 95 | 96 | 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /Tests/Functional/ImageEditTest.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | use Iphp\FileStoreBundle\Tests\Functional\TestBundle\Entity\Photo; 10 | 11 | 12 | class ImageEditTest extends BaseTestCase 13 | { 14 | public function testImageEditAndDelete() 15 | { 16 | $client = $this->createClient(); 17 | $this->importDatabaseSchema(); 18 | 19 | $this->getEntityManager()->persist($this->createTestPhotoObject()); 20 | $this->getEntityManager()->flush(); 21 | $this->assertLoadedPhotoParams($this->getEntityManager()->getRepository('TestBundle:Photo')->findOneById(1)); 22 | 23 | 24 | //go to edit photo via form 25 | $crawler = $client->request('GET', '/edit/1/'); 26 | $this->assertPhotoExistsInForm($crawler); 27 | 28 | 29 | 30 | 31 | //upload new files 32 | $newFileToUpload = new \Symfony\Component\HttpFoundation\File\UploadedFile( 33 | __DIR__ . '/../Fixtures/images/php-elephant.png', 'php-elephant.png'); 34 | $alsoNewFileToUpload = new \Symfony\Component\HttpFoundation\File\UploadedFile( 35 | __DIR__ . '/../Fixtures/images/github1.png', 'github1.png'); 36 | 37 | $form = $crawler->selectButton('Save')->form(); 38 | 39 | //this is combined field [file => [FileType file,Checkbox delete] ] 40 | $form['form[photo][file]']->upload($newFileToUpload); 41 | $form['form[photoUpload]']->upload($alsoNewFileToUpload); 42 | $client->submit($form); 43 | $crawler = $client->followRedirect(); 44 | $this->assertPreviousImageGone($crawler); 45 | 46 | //try to delete images 47 | $form = $crawler->selectButton('Save')->form(); 48 | $form['form[photo][delete]']->tick(); 49 | $form['form[photoInfo][delete]']->tick(); 50 | $client->submit($form); 51 | $crawler = $client->followRedirect(); 52 | $this->assertNoPhotoOnForm($crawler); 53 | 54 | $this->getEntityManager()->clear(); 55 | $photoAfterUpdate = $this->getEntityManager()->getRepository('TestBundle:Photo')->findOneById(1); 56 | $this->assertSame($photoAfterUpdate->getPhoto(), null); 57 | $this->assertSame($photoAfterUpdate->getPhotoInfo(), null); 58 | } 59 | 60 | function createTestPhotoObject() 61 | { 62 | //Creating photo objecy with 2 images 63 | $existsFile = new \Symfony\Component\HttpFoundation\File\File( 64 | __DIR__ . '/../Fixtures/images/front-images-list.jpeg'); 65 | 66 | $alsoExistsFile = new \Symfony\Component\HttpFoundation\File\File( 67 | __DIR__ . '/../Fixtures/images/sonata-admin-iphpfile.jpeg'); 68 | 69 | $photo = new Photo(); 70 | $photo->setTitle('Second photo') 71 | ->setDate(new \DateTime ('2013-04-05 00:00:00')) 72 | ->setPhoto($existsFile) 73 | ->setPhotoUpload($alsoExistsFile); 74 | 75 | return $photo; 76 | } 77 | 78 | function assertLoadedPhotoParams($photoLoaded) 79 | { 80 | //params of fixture files 81 | $this->assertSame($photoLoaded->getPhoto(), [ 82 | 'fileName' => '/2013/04/front-images-list.jpeg', 83 | 'originalName' => 'front-images-list.jpeg', 84 | 'mimeType' => 'image/jpeg', 85 | 'size' => 67521, 86 | 'path' => '/photo/2013/04/front-images-list.jpeg', 87 | 'width' => 445, 88 | 'height' => 531 89 | ]); 90 | 91 | $this->assertSame($photoLoaded->getPhotoInfo(), [ 92 | 'fileName' => '/2013/04/sonata-admin-iphpfile.jpeg', 93 | 'originalName' => 'sonata-admin-iphpfile.jpeg', 94 | 'mimeType' => 'image/jpeg', 95 | 'size' => 48332, 96 | 'path' => '/photo/2013/04/sonata-admin-iphpfile.jpeg', 97 | 'width' => 671, 98 | 'height' => 487 99 | ]); 100 | } 101 | 102 | function assertPhotoExistsInForm($crawler) 103 | { 104 | //check foto exists in form 105 | $this->assertSame($crawler->filter('input[id="form_title"][value="Second photo"]')->count(), 1); 106 | $this->assertSame($crawler->filter('option[value="2013"][selected="selected"]')->count(), 1); 107 | $this->assertSame($crawler->filter('option[value="4"][selected="selected"]')->count(), 1); 108 | 109 | 110 | //displayed loaded image and checkbox for delete image 111 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/front-images-list.jpeg"]')->count(), 1); 112 | $this->assertSame($crawler->filter('input[type="checkbox"][id="form_photo_delete"]')->count(), 1); 113 | 114 | 115 | //displayed loaded second image and checkbox for delete image 116 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/sonata-admin-iphpfile.jpeg"]')->count(), 1); 117 | $this->assertSame($crawler->filter('input[type="checkbox"][id="form_photoInfo_delete"]')->count(), 1); 118 | } 119 | 120 | function assertNoPhotoOnForm($crawler) 121 | { 122 | //after photo delete NOT displaying loaded image and checkbox for delete image 123 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/front-images-list.jpeg"]')->count(), 0); 124 | $this->assertSame($crawler->filter('input[type="checkbox"][id="form_photo_delete"]')->count(), 0); 125 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/sonata-admin-iphpfile.jpeg"]')->count(), 0); 126 | $this->assertSame($crawler->filter('input[type="checkbox"][id="form_photoInfo_delete"]')->count(), 0); 127 | } 128 | 129 | function assertPreviousImageGone($crawler) 130 | { 131 | //previous image gone 132 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/front-images-list.jpeg"]')->count(), 0); 133 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/sonata-admin-iphpfile.jpeg"]')->count(), 0); 134 | //new exists 135 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/php-elephant.png"]')->count(), 1); 136 | $this->assertSame($crawler->filter('img[src="/photo/2013/04/github1.png"]')->count(), 1); 137 | 138 | } 139 | } -------------------------------------------------------------------------------- /Tests/Functional/ImageUploadTest.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | use Iphp\FileStoreBundle\Tests\Functional\TestBundle\Entity\Photo; 10 | 11 | class ImageUploadTest extends BaseTestCase 12 | { 13 | 14 | 15 | public function testImageUpload() 16 | { 17 | $client = $this->createClient(); 18 | $this->importDatabaseSchema(); 19 | 20 | $client->enableProfiler(); 21 | $crawler = $client->request('GET', '/'); 22 | 23 | $this->assertTrue($client->getResponse()->isSuccessful()); 24 | //Photos not uploaded yet 25 | $this->assertSame($crawler->filter('div.photo')->count(), 0); 26 | 27 | 28 | $client->enableProfiler(); 29 | 30 | $fileToUpload = new \Symfony\Component\HttpFoundation\File\UploadedFile( 31 | __DIR__ . '/../Fixtures/images/sonata-admin-iphpfile.jpeg', 'sonata-admin-iphpfile.jpeg'); 32 | 33 | $client->submit($crawler->selectButton('Upload')->form(), array( 34 | 'title' => 'Some title', 35 | 'photo' => $fileToUpload, 36 | 'date[year]' => '2013', 37 | 'date[month]' => '3', 38 | 'date[day]' => '15' 39 | )); 40 | 41 | 42 | $crawler = $client->followRedirect(); 43 | 44 | //added 1 photo 45 | $this->assertSame($crawler->filter('div.photo')->count(), 1); 46 | 47 | 48 | $photos = $this->getEntityManager()->getRepository('TestBundle:Photo')->findAll(); 49 | $this->assertSame(sizeof($photos), 1); 50 | $photo = $photos[0]; 51 | $this->assertSame($photo->getTitle(), 'Some title'); 52 | 53 | //path to images dir and directory naming config in Tests/Functional/config/default.yml 54 | $this->assertSame($photo->getPhoto(), array( 55 | 56 | 'fileName' => '/2013/03/sonata-admin-iphpfile.jpeg', 57 | 'originalName' => 'sonata-admin-iphpfile.jpeg', 58 | 'mimeType' => 'application/octet-stream', 59 | 'size' => $fileToUpload->getSize(), 60 | 'path' => '/photo/2013/03/sonata-admin-iphpfile.jpeg', 61 | 'width' => 671, 62 | 'height' => 487 63 | )); 64 | } 65 | 66 | 67 | } -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class DefaultController extends Controller 19 | { 20 | public function indexAction(Request $request) 21 | { 22 | $em = $this->getDoctrine()->getManager(); 23 | $photo = new Photo(); 24 | 25 | $uploadForm = $this->get('form.factory')->createNamedBuilder(null, FormType::class, $photo, 26 | array('csrf_protection' => false)) 27 | ->add('title', TextType::class) 28 | ->add('date', DateType::class) 29 | //Using standart field type 30 | ->add('photo', FileType::class) 31 | ->add('photoUpload', FileType::class) 32 | ->getForm(); 33 | 34 | $uploadForm->handleRequest($request); 35 | 36 | if ($uploadForm->isSubmitted() && $uploadForm->isValid()) { 37 | 38 | $em->persist($photo); 39 | $em->flush(); 40 | return $this->redirect($this->generateUrl('photo_index')); 41 | 42 | } 43 | 44 | return $this->render('TestBundle:Photo:index.html.twig', array( 45 | 'uploadForm' => $uploadForm->createView(), 46 | 'photos' => $em->getRepository('TestBundle:Photo')->findAll() 47 | )); 48 | } 49 | 50 | 51 | public function editAction(Request $request, $id) 52 | { 53 | $em = $this->getDoctrine()->getManager(); 54 | $photo = $em->getRepository('TestBundle:Photo')->findOneById($id); 55 | 56 | $editForm = $this->createFormBuilder($photo) 57 | ->add('title', TextType::class) 58 | ->add('date', DateType::class) 59 | //Using field type with showing file/image info 60 | ->add('photo', IphpFileType::class) 61 | 62 | ->add('photoUpload', FileType::class) 63 | ->add('photoInfo', IphpFileType::class) 64 | 65 | ->getForm(); 66 | 67 | 68 | $editForm->handleRequest($request); 69 | 70 | if ($editForm->isSubmitted() && $editForm->isValid()) { 71 | $em->persist($photo); 72 | $em->flush(); 73 | return $this->redirect($this->generateUrl('photo_edit', array('id' => $photo->getId()))); 74 | 75 | } 76 | 77 | return $this->render('TestBundle:Photo:edit.html.twig', array( 78 | 79 | 'photo' => $photo, 80 | 'editForm' => $editForm->createView(), 81 | )); 82 | 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Entity/Photo.php: -------------------------------------------------------------------------------- 1 | id; 66 | } 67 | 68 | /** 69 | * @param string $title 70 | * @return Photo 71 | */ 72 | public function setTitle($title) 73 | { 74 | $this->title = $title; 75 | return $this; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getTitle() 82 | { 83 | return $this->title; 84 | } 85 | 86 | /** 87 | * @param array $photo 88 | * @return Photo 89 | */ 90 | public function setPhoto($photo) 91 | { 92 | $this->photo = $photo; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @return array 98 | */ 99 | public function getPhoto() 100 | { 101 | return $this->photo; 102 | } 103 | 104 | /** 105 | * @param \Datetime $date 106 | */ 107 | public function setDate($date) 108 | { 109 | $this->date = $date; 110 | return $this; 111 | } 112 | 113 | /** 114 | * @return \Datetime 115 | */ 116 | public function getDate() 117 | { 118 | return $this->date; 119 | } 120 | 121 | /** 122 | * @return mixed 123 | */ 124 | public function getPhotoUpload() 125 | { 126 | return $this->photoUpload; 127 | } 128 | 129 | /** 130 | * @param mixed $photoUpload 131 | */ 132 | public function setPhotoUpload($photoUpload) 133 | { 134 | $this->photoUpload = $photoUpload; 135 | } 136 | 137 | /** 138 | * @return mixed 139 | */ 140 | public function getPhotoInfo() 141 | { 142 | return $this->photoInfo; 143 | } 144 | 145 | /** 146 | * @param mixed $photoInfo 147 | */ 148 | public function setPhotoInfo($photoInfo) 149 | { 150 | $this->photoInfo = $photoInfo; 151 | } 152 | 153 | 154 | } 155 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Resources/views/Photo/edit.html.twig: -------------------------------------------------------------------------------- 1 |

Edit photo

2 | 3 |
4 | 5 | {{ form_widget(editForm) }} 6 | 7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /Tests/Functional/TestBundle/Resources/views/Photo/index.html.twig: -------------------------------------------------------------------------------- 1 | {# src/Iphpsandbox/PhotoBundle/Resources/views/Photo/index.html.twig #} 2 |

Photos

3 | 4 |

Uploaded photos

5 | 6 | {% for photo in photos %} 7 |
8 | 9 |

{{ photo.title }}

10 | 150 ? 'height="150"':''}}/> 11 | 12 |
{{ photo.date | date ('Y.m.d') }}
13 | 14 |
15 | {% endfor%} 16 | 17 |

Upload new photo

18 | 19 |
20 | 21 | {{ form_widget(uploadForm) }} 22 | 23 |
-------------------------------------------------------------------------------- /Tests/Functional/TestBundle/TestBundle.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class TestBundle extends Bundle 11 | { 12 | } -------------------------------------------------------------------------------- /Tests/Functional/TestXmlConfigBundle/Entity/File.php: -------------------------------------------------------------------------------- 1 | id; 45 | } 46 | 47 | /** 48 | * @param string $title 49 | * @return Photo 50 | */ 51 | public function setTitle($title) 52 | { 53 | $this->title = $title; 54 | return $this; 55 | } 56 | 57 | /** 58 | * @return string 59 | */ 60 | public function getTitle() 61 | { 62 | return $this->title; 63 | } 64 | 65 | 66 | /** 67 | * @param \Datetime $date 68 | */ 69 | public function setDate($date) 70 | { 71 | $this->date = $date; 72 | return $this; 73 | } 74 | 75 | /** 76 | * @return \Datetime 77 | */ 78 | public function getDate() 79 | { 80 | return $this->date; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/Functional/TestXmlConfigBundle/Entity/UploadableEntity.php: -------------------------------------------------------------------------------- 1 | 11 | * @FileStore\Uploadable 12 | */ 13 | 14 | abstract class UploadableEntity 15 | { 16 | 17 | 18 | /** 19 | * @Assert\Image( maxSize="20M") 20 | * @FileStore\UploadableField(mapping="file") 21 | **/ 22 | protected $file; 23 | 24 | public function setFile($file) 25 | { 26 | $this->file = $file; 27 | return $this; 28 | } 29 | 30 | public function getFile() 31 | { 32 | return $this->file; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/Functional/TestXmlConfigBundle/Resources/config/doctrine/File.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tests/Functional/TestXmlConfigBundle/TestXmlConfigBundle.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | class TestXmlConfigBundle extends Bundle 11 | { 12 | } -------------------------------------------------------------------------------- /Tests/Functional/config/database.php: -------------------------------------------------------------------------------- 1 | getParameter ('kernel.test_env_dir').'/db.sqllite'; 4 | 5 | 6 | $container->loadFromExtension('doctrine', array( 7 | 'dbal' => array( 8 | 'driver' => 'pdo_sqlite', 9 | 'path' => $path, 10 | ), 11 | )); -------------------------------------------------------------------------------- /Tests/Functional/config/default.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: framework.yml } 3 | - { resource: doctrine.yml } 4 | - { resource: twig.yml } 5 | - { resource: database.php } 6 | 7 | 8 | iphp_file_store: 9 | mappings: 10 | photo: 11 | upload_dir: %kernel.test_env_dir%/web/photo 12 | upload_path: /photo 13 | directory_namer: 14 | date: 15 | params: { field : date, depth : month } 16 | namer: ~ 17 | 18 | 19 | file: 20 | upload_dir: %kernel.test_env_dir%/web/file 21 | upload_path: /file 22 | 23 | #chain of directory namers 24 | directory_namer: 25 | entityName: ~ 26 | property: 27 | params: { use_field_name : true } 28 | date: 29 | params: { field : date, depth : year } 30 | namer: 31 | #naming file by id 32 | property: ~ 33 | 34 | -------------------------------------------------------------------------------- /Tests/Functional/config/default_newfilepath.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: framework.yml } 3 | - { resource: doctrine.yml } 4 | - { resource: twig.yml } 5 | - { resource: database.php } 6 | 7 | 8 | iphp_file_store: 9 | mappings: 10 | file: 11 | upload_dir: %kernel.test_env_dir%/web/other/uploads 12 | upload_path: /other/uploads 13 | 14 | #directory naming by id property 15 | directory_namer: 16 | property: ~ 17 | 18 | #file naming by translited title property 19 | namer: 20 | property: 21 | params: { field: title} 22 | translit: 23 | 24 | -------------------------------------------------------------------------------- /Tests/Functional/config/doctrine.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | doctrine: 4 | orm: 5 | auto_generate_proxy_classes: "%kernel.debug%" 6 | entity_managers: 7 | default: 8 | auto_mapping: true 9 | services: 10 | em: "@doctrine.orm.entity_manager" -------------------------------------------------------------------------------- /Tests/Functional/config/framework.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: test 3 | test: ~ 4 | session: 5 | storage_id: session.storage.mock_file 6 | form: true 7 | csrf_protection: true 8 | validation: 9 | enabled: true 10 | enable_annotations: true 11 | router: 12 | resource: "%kernel.root_dir%/config/routing.yml" -------------------------------------------------------------------------------- /Tests/Functional/config/routing.yml: -------------------------------------------------------------------------------- 1 | photo_index: 2 | path: / 3 | defaults: { _controller: TestBundle:Default:index } 4 | 5 | photo_edit: 6 | path: /edit/{id}/ 7 | defaults: { _controller: TestBundle:Default:edit } -------------------------------------------------------------------------------- /Tests/Functional/config/twig.yml: -------------------------------------------------------------------------------- 1 | framework: 2 | templating: 3 | engines: [twig, php] 4 | 5 | twig: 6 | debug: %kernel.debug% 7 | strict_variables: %kernel.debug% -------------------------------------------------------------------------------- /Tests/Mapping/Annotation/UploadableFieldTest.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UploadableFieldTest extends \PHPUnit_Framework_TestCase 13 | { 14 | /** 15 | * @var \Iphp\FileStoreBundle\Mapping\Annotation\UploadableField; 16 | */ 17 | protected $uploadableField; 18 | 19 | public function setUp() 20 | { 21 | $this->uploadableField = new UploadableField(array('mapping' => 'dummy_file')); 22 | } 23 | 24 | /** 25 | * @expectedException \InvalidArgumentException 26 | */ 27 | public function testExceptionThrownWhenNoMappingAttribute() 28 | { 29 | new UploadableField(array( 30 | 'fileNameProperty' => 'fileName' 31 | )); 32 | } 33 | 34 | 35 | public function testGetMapping() 36 | { 37 | 38 | $this->assertSame($this->uploadableField->getMapping(), 'dummy_file'); 39 | } 40 | 41 | 42 | public function testSetMapping() 43 | { 44 | $this->uploadableField->setMapping('dummy_file'); 45 | $this->assertSame($this->uploadableField->getMapping(), 'dummy_file'); 46 | } 47 | 48 | 49 | public function testGetSetFileUploadPropertyName() 50 | { 51 | $this->uploadableField->setFileUploadPropertyName('file'); 52 | $this->assertSame($this->uploadableField->getFileUploadPropertyName(), 'file'); 53 | $this->assertSame($this->uploadableField->getFileDataPropertyName(), 'file'); 54 | 55 | } 56 | 57 | 58 | 59 | 60 | public function testGetSetFileDataPropertyName() 61 | { 62 | $this->uploadableField->setFileUploadPropertyName('file'); 63 | $this->uploadableField->setFileDataPropertyName('file_data'); 64 | 65 | $this->assertSame($this->uploadableField->getFileUploadPropertyName(), 'file'); 66 | $this->assertSame($this->uploadableField->getFileDataPropertyName(), 'file_data'); 67 | } 68 | 69 | 70 | 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Tests/Mapping/PropertyMappingFactoryTest.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class PropertyMappingFactoryTest extends \PHPUnit_Framework_TestCase 15 | { 16 | 17 | /** 18 | * @var \Iphp\FileStoreBundle\Naming\NamerServiceInvoker 19 | */ 20 | protected $namerServiceInvoker; 21 | 22 | 23 | /** 24 | * @var \Iphp\FileStoreBundle\Driver\AnnotationDriver $driver 25 | */ 26 | protected $driver; 27 | 28 | 29 | /** 30 | * Sets up the test. 31 | */ 32 | public function setUp() 33 | { 34 | $this->driver = Mocks::getAnnotationDriverMock($this); 35 | $this->namerServiceInvoker = Mocks::getNamerServiceInvokerMock($this); 36 | } 37 | 38 | /** 39 | * if a non uploadable object is passed in - return array() 40 | * 41 | */ 42 | public function testFromObjectThrowsExceptionIfNotUploadable() 43 | { 44 | $obj = new \StdClass(); 45 | $class = new \ReflectionClass($obj); 46 | 47 | $this->driver 48 | ->expects($this->once()) 49 | ->method('readUploadable') 50 | ->will($this->returnValue(null)); 51 | 52 | $factory = new PropertyMappingFactory($this->namerServiceInvoker, $this->driver, array()); 53 | $this->assertEquals($factory->getMappingsFromObject($obj, $class), array()); 54 | } 55 | 56 | /** 57 | * Test the fromObject method with one uploadable 58 | * field. 59 | */ 60 | public function testFromObjectOneField() 61 | { 62 | $obj = new DummyEntity(); 63 | $class = new \ReflectionClass($obj); 64 | 65 | $mappingsConfig = array( 66 | 'dummy_file' => array( 67 | 'upload_dir' => '/www/web/images', 68 | 'upload_path' => '/images' 69 | ) 70 | ); 71 | 72 | $uploadable = Mocks::getUploadableMock($this); 73 | $fileField = Mocks::getUploadableFieldMock($this); 74 | 75 | $fileField 76 | ->expects($this->any()) 77 | ->method('getMapping') 78 | ->will($this->returnValue('dummy_file')); 79 | 80 | $fileField 81 | ->expects($this->once()) 82 | ->method('getFileUploadPropertyName') 83 | ->will($this->returnValue('file')); 84 | 85 | $fileField 86 | ->expects($this->once()) 87 | ->method('getFileDataPropertyName') 88 | ->will($this->returnValue('file')); 89 | 90 | 91 | $this->driver 92 | ->expects($this->once()) 93 | ->method('readUploadable') 94 | ->with($class) 95 | ->will($this->returnValue($uploadable)); 96 | 97 | $this->driver 98 | ->expects($this->once()) 99 | ->method('readUploadableFields') 100 | ->with($class) 101 | ->will($this->returnValue(array($fileField))); 102 | 103 | $factory = new PropertyMappingFactory($this->namerServiceInvoker, $this->driver, $mappingsConfig); 104 | $mappings = $factory->getMappingsFromObject($obj, $class); 105 | 106 | $this->assertEquals(1, count($mappings)); 107 | 108 | if (count($mappings) > 0) { 109 | $mapping = $mappings[0]; 110 | 111 | $this->assertEquals('dummy_file', $mapping->getMappingName()); 112 | $this->assertEquals('/www/web/images', $mapping->getUploadDir()); 113 | $this->assertEquals('/images', $mapping->getUploadPath()); 114 | $this->assertTrue($mapping->getDeleteOnRemove()); 115 | $this->assertFalse($mapping->hasNamer()); 116 | $this->assertFalse($mapping->hasDirectoryNamer()); 117 | $this->assertFalse($mapping->isStoreFullDir()); 118 | $this->assertFalse($mapping->isOverwriteDuplicates()); 119 | 120 | $this->assertSame($mapping->getObj(), $obj); 121 | 122 | } 123 | } 124 | 125 | /** 126 | * Test that an exception is thrown when an invalid mapping name 127 | * is specified. 128 | * 129 | * @expectedException \InvalidArgumentException 130 | */ 131 | public function testThrowsExceptionOnInvalidMappingName() 132 | { 133 | $obj = new DummyEntity(); 134 | $class = new \ReflectionClass($obj); 135 | 136 | $mappingsConfig = array( 137 | 'bad_name' => array() 138 | ); 139 | 140 | $uploadable = Mocks::getUploadableMock($this); 141 | $fileField = Mocks::getUploadableFieldMock($this); 142 | 143 | $fileField 144 | ->expects($this->any()) 145 | ->method('getMapping') 146 | ->will($this->returnValue('dummy_file')); 147 | 148 | 149 | $this->driver 150 | ->expects($this->once()) 151 | ->method('readUploadable') 152 | ->with($class) 153 | ->will($this->returnValue($uploadable)); 154 | 155 | $this->driver 156 | ->expects($this->once()) 157 | ->method('readUploadableFields') 158 | ->with($class) 159 | ->will($this->returnValue(array($fileField))); 160 | 161 | $factory = new PropertyMappingFactory($this->namerServiceInvoker, $this->driver, $mappingsConfig); 162 | $mappings = $factory->getMappingsFromObject($obj, $class); 163 | } 164 | 165 | /** 166 | * Test that the fromField method returns null when an invalid 167 | * field name is specified. 168 | */ 169 | public function testFromFieldReturnsNullOnInvalidFieldName() 170 | { 171 | $obj = new DummyEntity(); 172 | $class = new \ReflectionClass($obj); 173 | 174 | $uploadable = Mocks::getUploadableMock($this); 175 | 176 | $this->driver 177 | ->expects($this->once()) 178 | ->method('readUploadable') 179 | ->with($class) 180 | ->will($this->returnValue($uploadable)); 181 | 182 | $this->driver 183 | ->expects($this->once()) 184 | ->method('readUploadableField') 185 | ->with($class) 186 | ->will($this->returnValue(null)); 187 | 188 | $this->driver 189 | ->expects($this->once()) 190 | ->method('readUploadableFields') 191 | ->with($class) 192 | ->will($this->returnValue(array())); 193 | 194 | 195 | $factory = new PropertyMappingFactory($this->namerServiceInvoker, $this->driver, array()); 196 | $mapping = $factory->getMappingFromField($obj, $class, 'oops'); 197 | 198 | $this->assertNull($mapping); 199 | } 200 | 201 | 202 | /** 203 | */ 204 | public function testGetMappingFromField() 205 | { 206 | $obj = new DummyEntity(); 207 | $class = new \ReflectionClass($obj); 208 | 209 | $uploadable = Mocks::getUploadableMock($this); 210 | $fileField = Mocks::getUploadableFieldMock($this); 211 | 212 | 213 | $mappingsConfig = array( 214 | 'dummy_file' => array( 215 | 'upload_dir' => '/www/web/images', 216 | 'upload_path' => '/images' 217 | ) 218 | ); 219 | 220 | 221 | $fileField 222 | ->expects($this->any()) 223 | ->method('getMapping') 224 | ->will($this->returnValue('dummy_file')); 225 | 226 | $fileField 227 | ->expects($this->once()) 228 | ->method('getFileUploadPropertyName') 229 | ->will($this->returnValue('file')); 230 | 231 | $fileField 232 | ->expects($this->once()) 233 | ->method('getFileDataPropertyName') 234 | ->will($this->returnValue('file')); 235 | 236 | $this->driver 237 | ->expects($this->once()) 238 | ->method('readUploadable') 239 | ->with($class) 240 | ->will($this->returnValue($uploadable)); 241 | 242 | $this->driver 243 | ->expects($this->once()) 244 | ->method('readUploadableField') 245 | ->with($class) 246 | ->will($this->returnValue($fileField)); 247 | 248 | 249 | $factory = new PropertyMappingFactory($this->namerServiceInvoker, $this->driver, $mappingsConfig); 250 | $mapping = $factory->getMappingFromField($obj, $class, 'file'); 251 | 252 | $this->assertSame($mapping->getObj(), $obj); 253 | $this->assertEquals('dummy_file', $mapping->getMappingName()); 254 | $this->assertEquals('/www/web/images', $mapping->getUploadDir()); 255 | $this->assertEquals('/images', $mapping->getUploadPath()); 256 | $this->assertTrue($mapping->getDeleteOnRemove()); 257 | $this->assertFalse($mapping->hasNamer()); 258 | $this->assertFalse($mapping->hasDirectoryNamer()); 259 | $this->assertFalse($mapping->isStoreFullDir()); 260 | $this->assertFalse($mapping->isOverwriteDuplicates()); 261 | } 262 | 263 | 264 | public function testConfiguredNamerRetrievedFromInvoker() 265 | { 266 | $obj = new DummyEntity(); 267 | $class = new \ReflectionClass($obj); 268 | 269 | $mappingsConfig = array( 270 | 'dummy_file' => array( 271 | 'upload_dir' => '/www/web/images', 272 | 'upload_path' => '/images', 273 | 'namer' => array('translit' => array('service' => 'iphp.filestore.namer.default')) 274 | ) 275 | 276 | ); 277 | 278 | $uploadable = Mocks::getUploadableMock($this); 279 | $fileField = Mocks::getUploadableFieldMock($this); 280 | 281 | $fileField 282 | ->expects($this->any()) 283 | ->method('getMapping') 284 | ->will($this->returnValue('dummy_file')); 285 | 286 | $fileField 287 | ->expects($this->once()) 288 | ->method('getFileUploadPropertyName') 289 | ->will($this->returnValue('file')); 290 | 291 | $fileField 292 | ->expects($this->once()) 293 | ->method('getFileDataPropertyName') 294 | ->will($this->returnValue('file')); 295 | 296 | 297 | $this->driver 298 | ->expects($this->once()) 299 | ->method('readUploadable') 300 | ->with($class) 301 | ->will($this->returnValue($uploadable)); 302 | 303 | $this->driver 304 | ->expects($this->once()) 305 | ->method('readUploadableFields') 306 | ->with($class) 307 | ->will($this->returnValue(array($fileField))); 308 | 309 | $factory = new PropertyMappingFactory($this->namerServiceInvoker, $this->driver, $mappingsConfig); 310 | $mappings = $factory->getMappingsFromObject($obj, $class); 311 | 312 | $this->assertEquals(1, count($mappings)); 313 | 314 | if (count($mappings) > 0) { 315 | $mapping = $mappings[0]; 316 | 317 | $this->namerServiceInvoker 318 | ->expects($this->once()) 319 | ->method('rename') 320 | ->with('iphp.filestore.namer.default', 'translit', $mapping, 'ab cde', array()) 321 | ->will($this->returnValue('ab-cde')); 322 | 323 | 324 | $this->assertEquals($mapping->useFileNamer('ab cde'), 'ab-cde'); 325 | $this->assertTrue($mapping->hasNamer()); 326 | } 327 | } 328 | 329 | 330 | } 331 | -------------------------------------------------------------------------------- /Tests/Mapping/PropertyMappingTest.php: -------------------------------------------------------------------------------- 1 | namerServiceInvoker = Mocks::getNamerServiceInvokerMock($this); 29 | $this->fileStorage = Mocks::getFileStorageMock($this); 30 | } 31 | 32 | 33 | /** 34 | * @param $object 35 | * @param array $mergeConfig 36 | * @return PropertyMapping 37 | */ 38 | public function getPropertyMapping($object, $mergeConfig = array()) 39 | { 40 | $config = $object->defaultFileStoreConfig['dummy_file']; 41 | if ($mergeConfig) $config = array_merge($config, $mergeConfig); 42 | 43 | return new PropertyMapping($object, $config, $this->namerServiceInvoker); 44 | } 45 | 46 | public function testUseDirectoryNamerNoExists() 47 | { 48 | $propertyMapping = $this->getPropertyMapping(new DummyEntity()); 49 | 50 | $this->assertFalse($propertyMapping->hasDirectoryNamer()); 51 | $this->assertSame($propertyMapping->useDirectoryNamer('file name', 'file name'), ''); 52 | } 53 | 54 | 55 | public function testUseDirectoryNamerExists() 56 | { 57 | $propertyMapping = $this->getPropertyMapping(new DummyEntity(), 58 | array('directory_namer' => 59 | array('entityName' => array('service' => 'iphp.filestore.directory_namer.default')))); 60 | 61 | 62 | $this->assertTrue($propertyMapping->hasDirectoryNamer()); 63 | 64 | $this->namerServiceInvoker 65 | ->expects($this->once()) 66 | ->method('rename') 67 | ->with('iphp.filestore.directory_namer.default', 'entityName', $propertyMapping, 'file name', array()) 68 | ->will($this->returnValue('DummyFile')); 69 | 70 | $this->assertSame($propertyMapping->useDirectoryNamer('file name', 'file name'), '/DummyFile'); 71 | } 72 | 73 | 74 | public function testNeedResolveCollisionFileNoExists() 75 | { 76 | $propertyMapping = $this->getPropertyMapping(new DummyEntity()); 77 | 78 | $this->fileStorage 79 | ->expects($this->once()) 80 | ->method('fileExists') 81 | ->with( '/www/web/images/123') 82 | ->will($this->returnValue(false)); 83 | 84 | 85 | 86 | 87 | 88 | 89 | //by default overwriteDuplicates = false 90 | $this->assertFalse($propertyMapping->needResolveCollision('123', $this->fileStorage)); 91 | } 92 | 93 | 94 | public function testNeedResolveCollisionOverwriteDuplicates() 95 | { 96 | $propertyMapping = $this->getPropertyMapping(new DummyEntity(), array('overwrite_duplicates' => true)); 97 | $this->fileStorage->expects($this->never())->method('fileExists'); 98 | 99 | //by default overwriteDuplicates = false 100 | $this->assertFalse($propertyMapping->needResolveCollision('123', $this->fileStorage)); 101 | } 102 | 103 | 104 | public function testPrepareFileName() 105 | { 106 | $propertyMapping = $this->getPropertyMapping(new DummyEntity()); 107 | 108 | $this->namerServiceInvoker 109 | ->expects($this->once()) 110 | ->method('rename') 111 | ->with('iphp.filestore.namer.default', 'translit', $propertyMapping, 'ab cd', array()) 112 | ->will($this->returnValue('ab-cd')); 113 | 114 | 115 | $this->fileStorage 116 | ->expects($this->once()) 117 | ->method('fileExists') 118 | ->with('/www/web/images/ab-cd') 119 | ->will($this->returnValue(false)); 120 | 121 | 122 | //Default false 123 | $this->assertFalse($propertyMapping->isStoreFullDir()); 124 | //From config 125 | $this->assertEquals($propertyMapping->getUploadPath(), '/images'); 126 | 127 | $this->assertSame($propertyMapping->prepareFileName('ab cd', $this->fileStorage), 128 | array('/ab-cd', '/images/ab-cd')); 129 | } 130 | 131 | 132 | /** 133 | * Once resolved collision 134 | */ 135 | public function testPrepareFileNameWithResolveCollision() 136 | { 137 | $propertyMapping = $this->getPropertyMapping(new DummyEntity()); 138 | 139 | 140 | //service iphp.filestore.namer.default, method translit will invoke once, 141 | //"ab cd" will be translited to "ab-cd" 142 | $this->namerServiceInvoker 143 | ->expects($this->once()) 144 | ->method('rename') 145 | ->with('iphp.filestore.namer.default', 'translit', $propertyMapping, 'ab cd', array()) 146 | ->will($this->returnValue('ab-cd')); 147 | 148 | 149 | $this->namerServiceInvoker 150 | ->expects($this->once()) 151 | ->method('resolveCollision') 152 | ->with('iphp.filestore.namer.default', 'ab-cd', 1) 153 | ->will($this->returnValue('ab-cd_1')); 154 | 155 | 156 | $fileExistsCalls = 0; 157 | 158 | $this->fileStorage 159 | ->expects($this->any()) 160 | ->method('fileExists') 161 | 162 | ->will($this->returnCallback(function() use (&$fileExistsCalls) 163 | { 164 | $args = func_get_args(); 165 | $fileExistsCalls++; 166 | return $args[0] == '/www/web/images/ab-cd' ? true : false; 167 | })); 168 | 169 | 170 | $this->assertSame($propertyMapping->prepareFileName('ab cd', $this->fileStorage), 171 | array('/ab-cd_1', '/images/ab-cd_1')); 172 | 173 | $this->assertSame($fileExistsCalls, 2); 174 | 175 | } 176 | 177 | 178 | /** 179 | * Not resolved collision 180 | * @expectedException \Exception 181 | */ 182 | public function testPrepareFileNameWithoutResolveCollision() 183 | { 184 | $propertyMapping = $this->getPropertyMapping(new DummyEntity()); 185 | 186 | //service iphp.filestore.namer.default, method translit will invoke once, 187 | //"ab cd" will be translited to "ab-cd" 188 | $this->namerServiceInvoker 189 | ->expects($this->once()) 190 | ->method('rename') 191 | ->with('iphp.filestore.namer.default', 'translit', $propertyMapping, 'ab cd', array()) 192 | ->will($this->returnValue('ab-cd')); 193 | 194 | 195 | //always return same name - no resolve 196 | $this->namerServiceInvoker 197 | ->expects($this->any()) 198 | ->method('resolveCollision') 199 | ->will($this->returnValue('ab-cd')); 200 | 201 | $this->fileStorage 202 | ->expects($this->any()) 203 | ->method('fileExists') 204 | ->will($this->returnCallback(function() 205 | { 206 | $args = func_get_args(); 207 | 208 | return $args[0] == '/www/web/images/ab-cd' ? true : false; 209 | })); 210 | 211 | 212 | $propertyMapping->prepareFileName('ab cd', $this->fileStorage); 213 | 214 | } 215 | 216 | 217 | /** 218 | * Exception about need namer for resolving collisions 219 | * @expectedException \Exception 220 | */ 221 | function testResolveFileCollisionNoNamer() 222 | { 223 | $propertyMapping = $this->getPropertyMapping(new DummyEntity(), array('namer' => null)); 224 | 225 | $this->namerServiceInvoker->expects($this->never())->method('rename'); 226 | $this->fileStorage 227 | ->expects($this->once()) 228 | ->method('fileExists') 229 | //No translited 230 | ->with('/www/web/images/ab cd') 231 | ->will($this->returnValue(true)); 232 | 233 | 234 | $propertyMapping->prepareFileName('ab cd', $this->fileStorage); 235 | } 236 | 237 | 238 | public function testGetPropertyName() 239 | { 240 | 241 | $obj = new DummyEntitySeparateDataField(); 242 | $class = new \ReflectionClass($obj); 243 | $file = Mocks::getFileMock($this); 244 | 245 | 246 | $propertyMapping = $this->getPropertyMapping($obj); 247 | 248 | $propertyMapping->setFileUploadProperty($class->getProperty('file')); 249 | $this->assertSame($propertyMapping->getFileUploadPropertyName(), 'file'); 250 | 251 | $propertyMapping->setFileDataProperty($class->getProperty('file_data')); 252 | $this->assertSame($propertyMapping->getFileDataPropertyName(), 'file_data'); 253 | 254 | 255 | $propertyMapping->setFileUploadPropertyValue($file); 256 | $this->assertSame( $propertyMapping->getFileUploadPropertyValue(), $file); 257 | 258 | 259 | $propertyMapping->setFileDataPropertyValue(array(1)); 260 | $this->assertSame( $propertyMapping->getFileDataPropertyValue(), array(1)); 261 | } 262 | 263 | 264 | 265 | public function testResolveFileName() 266 | { 267 | $obj = new DummyEntitySeparateDataField(); 268 | $class = new \ReflectionClass($obj); 269 | 270 | 271 | 272 | 273 | $propertyMapping = $this->getPropertyMapping($obj); 274 | 275 | $propertyMapping->setFileDataProperty($class->getProperty('file_data')); 276 | $propertyMapping->setFileDataPropertyValue(array('fileName' => 123)); 277 | 278 | 279 | $this->assertSame ($propertyMapping->resolveFileName(), $propertyMapping->getUploadDir() . '/123'); 280 | 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /Tests/Mocks.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | use Iphp\FileStoreBundle\Naming\NamerServiceInvoker; 9 | use Iphp\FileStoreBundle\Driver\AnnotationDriver; 10 | 11 | class Mocks 12 | { 13 | /** 14 | * Creates a mock args 15 | * 16 | * @return \Doctrine\Common\EventArgs EventArgs 17 | */ 18 | static function getEventArgsMock(\PHPUnit_Framework_TestCase $testCase) 19 | { 20 | return $testCase->getMockBuilder('Doctrine\Common\EventArgs') 21 | ->disableOriginalConstructor() 22 | ->getMock(); 23 | } 24 | 25 | 26 | static function getContainerMock(\PHPUnit_Framework_TestCase $testCase) 27 | { 28 | return $testCase->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface') 29 | ->disableOriginalConstructor() 30 | ->getMock(); 31 | } 32 | 33 | 34 | /** 35 | * Creates a mock uploadable 36 | * 37 | * @return \Iphp\FileStoreBundle\Mapping\Annotation\Uploadable Uploadable 38 | */ 39 | static function getUploadableMock(\PHPUnit_Framework_TestCase $testCase) 40 | { 41 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\Mapping\Annotation\Uploadable') 42 | ->disableOriginalConstructor() 43 | ->getMock(); 44 | } 45 | 46 | 47 | /** 48 | * Creates a mock uploadable field 49 | * 50 | * @return \Iphp\FileStoreBundle\Mapping\Annotation\UploadableField UploadableField 51 | */ 52 | static function getUploadableFieldMock(\PHPUnit_Framework_TestCase $testCase) 53 | { 54 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\Mapping\Annotation\UploadableField') 55 | ->disableOriginalConstructor() 56 | ->getMock(); 57 | } 58 | 59 | /** 60 | * Creates a mock property mapping 61 | * 62 | * @return \Iphp\FileStoreBundle\Mapping\PropertyMapping UploadableField 63 | */ 64 | static function getPropertyMappingMock(\PHPUnit_Framework_TestCase $testCase) 65 | { 66 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\Mapping\PropertyMapping') 67 | ->disableOriginalConstructor() 68 | ->getMock(); 69 | } 70 | 71 | 72 | /** 73 | * Creates a mock annotation driver. 74 | * 75 | * @return \Iphp\FileStoreBundle\Driver\AnnotationDriver The driver. 76 | */ 77 | static function getAnnotationDriverMock(\PHPUnit_Framework_TestCase $testCase) 78 | { 79 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\Driver\AnnotationDriver') 80 | ->disableOriginalConstructor() 81 | ->getMock(); 82 | } 83 | 84 | 85 | /** 86 | * Creates a mock for namer service invoker. 87 | * 88 | * @return \Iphp\FileStoreBundle\Naming\NamerServiceInvoker mock namer service invoker 89 | */ 90 | static function getNamerServiceInvokerMock(\PHPUnit_Framework_TestCase $testCase) 91 | { 92 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\Naming\NamerServiceInvoker') 93 | ->disableOriginalConstructor() 94 | ->getMock(); 95 | } 96 | 97 | /** 98 | * Creates a mock data storage 99 | * 100 | * @return \Iphp\FileStoreBundle\DataStorage\DataStorageInterface The mock data storage 101 | */ 102 | static function getDataStorageMock(\PHPUnit_Framework_TestCase $testCase) 103 | { 104 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\DataStorage\DataStorageInterface') 105 | ->disableOriginalConstructor() 106 | ->getMock(); 107 | } 108 | 109 | 110 | /** 111 | * Creates a mock storage. 112 | * 113 | * @return \Iphp\FileStoreBundle\FileStorage\FileStorageInterface mock file storage 114 | */ 115 | static function getFileStorageMock(\PHPUnit_Framework_TestCase $testCase) 116 | { 117 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\FileStorage\FileStorageInterface') 118 | ->disableOriginalConstructor() 119 | ->getMock(); 120 | } 121 | 122 | 123 | /** 124 | * Creates a mock mapping factory. 125 | * 126 | * @return \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory The factory. 127 | */ 128 | static function getPropertyMappingFactoryMock(\PHPUnit_Framework_TestCase $testCase, 129 | NamerServiceInvoker $namerServiceInvoker, 130 | AnnotationDriver $driver, 131 | $mappingsConfig = array()) 132 | { 133 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\Mapping\PropertyMappingFactory') 134 | ->setConstructorArgs(array($namerServiceInvoker, $driver, $mappingsConfig)) 135 | ->getMock(); 136 | 137 | } 138 | 139 | /** 140 | * Creates a mock file 141 | * 142 | * @return \Symfony\Component\HttpFoundation\File\File The file. 143 | */ 144 | static function getFileMock(\PHPUnit_Framework_TestCase $testCase) 145 | { 146 | return $testCase->getMockBuilder('Symfony\Component\HttpFoundation\File\File') 147 | ->setConstructorArgs(array(null, false)) 148 | ->getMock(); 149 | } 150 | 151 | 152 | /** 153 | * Creates a mock of uploadedFile 154 | * 155 | * @return \Symfony\Component\HttpFoundation\File\UploadedFile uploaded file 156 | */ 157 | static function getUploadedFileMock(\PHPUnit_Framework_TestCase $testCase) 158 | { 159 | return $testCase->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') 160 | ->disableOriginalConstructor() 161 | ->getMock(); 162 | } 163 | 164 | 165 | /** 166 | * Creates a mock file 167 | * 168 | * @return \Iphp\FileStoreBundle\File\File The file. 169 | */ 170 | static function getIphpFileMock(\PHPUnit_Framework_TestCase $testCase) 171 | { 172 | return $testCase->getMockBuilder('Iphp\FileStoreBundle\File\File') 173 | ->setConstructorArgs(array(null, false)) 174 | ->getMock(); 175 | } 176 | 177 | 178 | } 179 | -------------------------------------------------------------------------------- /Tests/Naming/DefaultDirectoryNamerTest.php: -------------------------------------------------------------------------------- 1 | namer = new DefaultDirectoryNamer; 18 | } 19 | 20 | 21 | protected function getMappingWithObject($object) 22 | { 23 | $propertyMapping = Mocks::getPropertyMappingMock($this); 24 | $propertyMapping->expects($this->any()) 25 | ->method('getObj') 26 | ->will($this->returnValue($object)); 27 | 28 | return $propertyMapping; 29 | } 30 | 31 | 32 | public function testPropertyRenameById() 33 | { 34 | 35 | $obj = new DummyEntity(); 36 | $obj->setId(12345); 37 | $propertyMapping = $this->getMappingWithObject($obj); 38 | 39 | $this->assertSame($this->namer->propertyRename($propertyMapping, 'some-name.jpg', array()), '12345'); 40 | 41 | } 42 | 43 | 44 | public function testPropertyRenameByMultiple() 45 | { 46 | $obj = new DummyEntity(); 47 | $obj->setId(12345); 48 | $file = Mocks::getFileMock($this); 49 | $file->expects($this->once()) 50 | ->method('getSize') 51 | ->will($this->returnValue('100')); 52 | 53 | $obj->setFile($file); 54 | $propertyMapping = $this->getMappingWithObject($obj); 55 | 56 | $this->assertSame($this->namer->propertyRename($propertyMapping, 'some-name.jpg', array( 57 | 'field' => 'id/file.size' 58 | )), '12345/100'); 59 | 60 | } 61 | 62 | 63 | 64 | 65 | public function testEntityNameRename() 66 | { 67 | $propertyMapping = $this->getMappingWithObject(new DummyEntity()); 68 | $this->assertSame($this->namer->entityNameRename($propertyMapping, 'some-name.jpg', array()), 'DummyEntity'); 69 | } 70 | 71 | 72 | public function testReplaceRename() 73 | { 74 | $propertyMapping = $this->getMappingWithObject(new DummyEntity()); 75 | $this->assertSame($this->namer->replaceRename($propertyMapping, 'some-dir', 76 | array('some' => 'new')), 'new-dir'); 77 | } 78 | 79 | 80 | public function testDateRename() 81 | { 82 | $obj = new DummyEntity(); 83 | $obj->setCreatedAt(new \DateTime ('2013-05-21 12:12:12')); 84 | $propertyMapping = $this->getMappingWithObject($obj); 85 | 86 | 87 | $this->assertSame($this->namer->dateRename($propertyMapping, 'some-dir', 88 | array('field' => 'createdAt')), '2013/05/21'); 89 | } 90 | 91 | 92 | public function testDateRenameByYear() 93 | { 94 | $obj = new DummyEntity(); 95 | $obj->setCreatedAt(new \DateTime ('2013-05-21 12:12:12')); 96 | $propertyMapping = $this->getMappingWithObject($obj); 97 | 98 | 99 | $this->assertSame($this->namer->dateRename($propertyMapping, 'some-dir', 100 | array('field' => 'createdAt', 'depth' => 'year')), '2013'); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Tests/Naming/DefaultNamerTest.php: -------------------------------------------------------------------------------- 1 | namer = new DefaultNamer; 22 | } 23 | 24 | 25 | protected function getMappingWithObject($object) 26 | { 27 | $propertyMapping = Mocks::getPropertyMappingMock($this); 28 | $propertyMapping->expects($this->any()) 29 | ->method('getObj') 30 | ->will($this->returnValue($object)); 31 | 32 | return $propertyMapping; 33 | } 34 | 35 | 36 | public function testTranslitRename() 37 | { 38 | $propertyMapping = Mocks::getPropertyMappingMock($this); 39 | $this->assertSame($this->namer->translitRename($propertyMapping, 40 | 'АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя'), 41 | 'aabbvvggddeeeezzzziijjkkllmmnnoopprrssttuuffhhccccssssyyeeuuaa'); 42 | 43 | 44 | $this->assertSame($this->namer->translitRename($propertyMapping, 45 | 'En Français on a des caractères accentués !'), 46 | 'en-francais-on-a-des-caracteres-accentues'); 47 | 48 | 49 | $this->assertSame($this->namer->translitRename($propertyMapping, ' ! , Давай-Давай .'), 50 | 'davaj-davaj-.'); 51 | } 52 | 53 | 54 | public function testPropertyRename() 55 | { 56 | $obj = new DummyEntity(); 57 | $obj->setId(12345); 58 | 59 | $propertyMapping = $this->getMappingWithObject($obj); 60 | $this->assertSame($this->namer->propertyRename($propertyMapping, 'some-name.jpg', array()), '12345.jpg'); 61 | } 62 | 63 | 64 | public function testPropertyRenameByField() 65 | { 66 | $obj = new DummyEntity(); 67 | $obj->setTitle('some-title'); 68 | 69 | $propertyMapping = $this->getMappingWithObject($obj); 70 | $this->assertSame( 71 | $this->namer->propertyRename($propertyMapping, 'some-name.jpg', array('field' => 'title')), 72 | 'some-title.jpg'); 73 | } 74 | 75 | 76 | public function testPropertyPrefixRename() 77 | { 78 | $obj = new DummyEntity(); 79 | $obj->setId(12345); 80 | 81 | $propertyMapping = $this->getMappingWithObject($obj); 82 | $this->assertSame( 83 | $this->namer->propertyPrefixRename($propertyMapping, 'some-name.jpg', array()), 84 | '12345-some-name.jpg'); 85 | } 86 | 87 | public function testPropertyPrefixRenameWithParams() 88 | { 89 | $obj = new DummyEntity(); 90 | 91 | $propertyMapping = $this->getMappingWithObject($obj); 92 | $propertyMapping->expects($this->once()) 93 | ->method('getFileDataPropertyName') 94 | ->will($this->returnValue('file')); 95 | 96 | $this->assertSame($this->namer->propertyPrefixRename($propertyMapping, 'some-name.jpg', 97 | array('use_field_name' => true, 'delimiter' => '_')), 'file_some-name.jpg'); 98 | } 99 | 100 | 101 | public function testPropertyPostfixRename() 102 | { 103 | $obj = new DummyEntity(); 104 | $obj->setTitle('some-title'); 105 | 106 | $propertyMapping = $this->getMappingWithObject($obj); 107 | 108 | $this->assertSame($this->namer->propertyPostfixRename($propertyMapping, 'some-name.jpg', 109 | array('field' => 'title')), 'some-name-some-title.jpg'); 110 | } 111 | 112 | 113 | public function testReplaceRename() 114 | { 115 | $obj = new DummyEntity(); 116 | $propertyMapping = $this->getMappingWithObject($obj); 117 | 118 | 119 | $this->assertSame($this->namer->replaceRename($propertyMapping, 'some-name.jpg', 120 | array('name' => 'staff')), 'some-staff.jpg'); 121 | } 122 | 123 | 124 | public function testResolveCollision() 125 | { 126 | $this->assertSame($this->namer->resolveCollision('some-name.jpg', 5), 'some-name_5.jpg'); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Tests/Naming/NamerServiceInvokerTest.php: -------------------------------------------------------------------------------- 1 | container = Mocks::getContainerMock($this); 23 | $this->invoker = new NamerServiceInvoker ($this->container); 24 | } 25 | 26 | 27 | public function testRename() 28 | { 29 | $propertyMapping = Mocks::getPropertyMappingMock($this); 30 | 31 | $namer = $this->getMockBuilder('Iphp\FileStoreBundle\Naming\DefaultNamer') 32 | ->disableOriginalConstructor() 33 | ->getMock(); 34 | 35 | 36 | $this->container 37 | ->expects($this->once()) 38 | ->method('get') 39 | ->with('test.service') 40 | ->will($this->returnValue($namer)); 41 | 42 | 43 | $namer->expects($this->once()) 44 | ->method('translitRename') 45 | ->with($propertyMapping, 'sample file name', array('param' => 1)) 46 | ->will($this->returnValue('sample-file-name')); 47 | 48 | $this->assertSame( 49 | $this->invoker->rename('test.service', 'translit', $propertyMapping, 'sample file name', array('param' => 1)), 50 | 'sample-file-name' 51 | ); 52 | 53 | 54 | } 55 | 56 | 57 | public function testResolveCollision() 58 | { 59 | $namer = $this->getMockBuilder('Iphp\FileStoreBundle\Naming\DefaultNamer') 60 | ->disableOriginalConstructor() 61 | ->getMock(); 62 | 63 | 64 | $this->container 65 | ->expects($this->once()) 66 | ->method('get') 67 | ->with('test.service') 68 | ->will($this->returnValue($namer)); 69 | 70 | 71 | $namer->expects($this->once()) 72 | ->method('resolveCollision') 73 | ->with('sample-file-name', 1) 74 | ->will($this->returnValue('sample-file-name_1')); 75 | 76 | 77 | $this->assertSame($this->invoker->resolveCollision('test.service', 'sample-file-name', 1), 78 | 'sample-file-name_1'); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Tests/TwoFieldsDummyEntity.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class TwoFieldsDummyEntity 13 | { 14 | /** 15 | * @FileStore\UploadableField(mapping="dummy_file") 16 | */ 17 | protected $file; 18 | 19 | /** 20 | * @FileStore\UploadableField(mapping="dummy_image") 21 | */ 22 | protected $image; 23 | 24 | public function getFile() 25 | { 26 | $this->file; 27 | } 28 | 29 | public function setFile($file) 30 | { 31 | $this->file = $file; 32 | } 33 | 34 | public function getImage() 35 | { 36 | $this->image; 37 | } 38 | 39 | public function setImage($image) 40 | { 41 | $this->image = $image; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ./Tests 8 | 9 | 10 | 11 | 12 | 13 | ./ 14 | 15 | ./Resources 16 | ./Tests 17 | ./vendor 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------