Бандл IphpFileStoreBundle создан на основе VichUploaderBundle. Т.к. все переписано достаточно сильно данному бандлу дано собственное название.
Бандл предназначен для загрузки файлов (symfony2 file upload)и хранения информации о них в БД посредством Doctrine ORM. По сравнения с бандлом VichUploaderBundle добавлено/изменено:
1. Добавлен виджет формы iphp_file, который позволяет загрузить новый файл, удалить загруженный файл и выводит информацию о текущем файле. Данный виджет можно использовать при создании интерфейса редактирования в Sonata Admin Bundle.
2. В БД хранится не название файла, а массив с информацией о файле — директория хранения, путь на сайте, название файла, оригинальное название файла до переименования, размер файла. Для изображений — ширина и высота изображения. Соответственно тип поля в БД — array.
3. Полностью переработан интерфейс Namer’ов для файлов и директорий. Добавлен код переименования файлов в случае совпадения имен.
Пример использования бандла для загрузки фотографий в Symfony2 можно посмотреть тут.
Инсталляция
Получение кода бандла
Пример фрагмента composer.json для установки через composer
"require":{
...
"iphp/filestore-bundle":"dev-master"
}Инициализация
// app/AppKernel.php public function registerBundles() { $bundles = array( // ... new Iphp\FileStoreBundle\IphpFileStoreBundle(), ); )
Конфигурация
Для хранения файлов нужно создать маппинг, в котором нужно указать upload_dir — директорию, куда будут загружаться файлы и upload_path — путь к этой директории на сайте. Минимальный пример конфигурации, для хранения изображения, присоединяемого к сущности:
# app/config/config.yml
iphp_file_store:
mappings:
image:
upload_dir: %kernel.root_dir%/../web/image
upload_path: /imageДругие опции:
- delete_on_remove — Удалять файлы при удалении записи в БД, в которой есть ссылка на этот файл. По умолчанию true.
- namer — сервис переименования файлов при загрузке. По умолчанию используется сервис с id = iphp.filestore.namer.default, метод сервиса translit — все русские буквы в названии файла заменяются транслитерацией.
- directory_namer — сервис создания поддиректорий для файлов при загрузке. По умолчанию поддиректорий не создается. В примере выше используется метод propertyRenamer сервиса по умолчанию с id= iphp.filestore.directory_namer.default. Этот метод создает поддиректорию по значению атрибута сущности, в данном примере — используется атрибут id связанной сущности content. Чтобы не создавать поддиректории нужно просто убрать directory_namer из конфигурации
- overwrite_duplicates — перезаписывать файлы при совпадении имен. По умолчанию false. При совпадении имени файл переименовывается в {название файла}_{номер}.{расширение файла}
Аннотации для сущностей
Для того чтобы связать сущность и конфигурацию загрузки файла (маппинг) в класс сущности нужно добавить аннотации. В начало класса нужно добавить аннотацию @FileStore\Uploadable, которая информирует систему о том что в данном классе содержатся аннотации для загрузки файлов. Затем, к каждому атрибуту, в который будут загружаться файлы нужно добавить аннотацию @FileStore\UploadableField, в параметре которой нужно указать как минимум название маппинга, который будет использоваться для загрузки файлов (маппинг был создан на предыдущем шаге). Пример сущности с аннотациями:
Важно! Тип поля, в котором сохраняется информация о загруженном файле, должен быть array (тип поля указывается в аннотациях или xml-конфи
<?php #src/Iphpsandbox/PhotoBundle/Entity/Photo.php namespace Iphpsandbox\PhotoBundle\Entity; use Iphp\FileStoreBundle\Mapping\Annotation as FileStore; use Symfony\Component\Validator\Constraints as Assert; /** * @FileStore\Uploadable */ class Photo { /** * @var integer */ private $id; /** * @var string */ private $title; /** * @var \Datetime */ private $date; /** * @Assert\File( maxSize="20M") * @FileStore\UploadableField(mapping="photo") **/ private $photo; ... /* Getters and setters */ ... }
Загрузка файлов
Создание формы загрузки файлов
Пример создания в контроллере формы для загрузки файла
<?php // src/Iphpsandbox/PhotoBundle/Controller/PhotoController.php namespace Iphpsandbox\PhotoBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Iphpsandbox\PhotoBundle\Entity\Photo; use Symfony\Component\HttpFoundation\Request; class PhotoController extends Controller { public function indexAction(Request $request) { $em = $this->getDoctrine()->getManager(); $photo = new Photo(); $uploadForm = $this->createFormBuilder($photo) ->add('title') ->add('date','date') //Using standart field type ->add ('photo','file') ->getForm(); if ($request->isMethod('POST')) { $uploadForm->bind($request); if ($uploadForm->isValid()) { $em->persist($photo); $em->flush(); return $this->redirect($this->generateUrl('iphpsandbox_photo')); } } return $this->render('IphpsandboxPhotoBundle:Photo:index.html.twig', array( 'uploadForm' => $uploadForm->createView(), 'photos' => $em->getRepository ('IphpsandboxPhotoBundle:Photo')->findAll() )); } }
Загрузка файлов в административном интерфейсе (SonataAdminBundle)
Подробно процесс создания административного интерфейса с помощью SonataAdminBundle описан тут, пример административного интерфейса для загрузки файлов приведен в Загрузка файлов в Symfony 2. Для редактирования фотографий в административном интерфейс нужно создать класс PhotoAdmin, в котором при создании формы используется тип поля iphp_file:
<?php #src/Iphpsandbox/PhotoBundle/Admin/PhotoAdmin.php namespace Iphpsandbox\PhotoBundle\Admin; use Sonata\AdminBundle\Admin\Admin; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Datagrid\ListMapper; class PhotoAdmin extends Admin { protected function configureListFields(ListMapper $listMapper) { return $listMapper->addIdentifier('title')->add ('date'); } protected function configureFormFields(FormMapper $formMapper) { return $formMapper->add('title')->add ('date')->add('photo', 'iphp_file'); } }
Страница редактирования сущности с присоединенным файлов выглядит так:
URL и другие данные загруженных файлов
Поле, которое используется для загрузки файла, после сохранения содержит массив данных о файле, ключи массива:
- path — полный путь к файлу на сайте
- size — размер файла в байт
- fileName — путь к файлу, относительно корневой директории маппинга
- originalName — оригинальное название файла, до переименования
- mimeType — mime тип файла
Если файл является изображением, тогда в массиве содержатся еще данные:
- width — ширина изображения
- height — высота изображения
Пример получения данных загруженного файла в PHP:
$photo = ... // загрузка сущности из БД $photoData = $photo->getPhoto(); //массив с данными файла $path = $photoData['path']; //Или для PHP 5.4. $path = $photo->getPhoto()['path'];
В Twig-шаблоне:
<img src="{{ photo.photo.path }}" alt="{{ photo.title}}" />Переименование загружаемых файлов
Для переименования загруженных файлов могут использоваться как входящие в состав бандла namer’ы, так и создаваемые пользователем. Namer’ы могут объединяться в цепочки, на вход следущему namer’у возвращается результат, возвращенный предыдущим.
Транслитерация
Наиболее часто необходимое переименование загружаемых файлов — это транслитерация кириллических знаков, пробелов и других символов, которые заменяются на коды в URL. Транслитерация используется по умолчанию. Чтобы ее отключить в значении параметра namer нужно установить false
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
...
namer: falseИменование файла по значению атрибута сущности
Название загруженного файла можно автоматически создавать на основе значения атрибута сущности (Entity) , к которой загружается файл. Например, можно именовать файл по названию сущности (namer называется translit, поле сущности, из которого берется строка — title). Затем к названию применяется транслитерация, для чего вторым namer’ом указан translit.
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
namer:
property:
params: { field : title }
translit: ~Добавление к названию файла значения атрибута сущности
Название файла можно не только полностью заменять, до и добавлять в начало или конец названия файла значение атрибута сущности. Например, для того чтобы названия файлов в директории не дублировались, можно добавлять префикс к названию файла, содержащий идентификатор записи (поле id, между идентификатором и названием файла ставится разделитель _):
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
namer:
translit: ~
propertyPrefix: #propertyPostfix - для того чтобы добавить строку в конец файла
params: { field : id, delimiter: "_" }Один и тот же маппинг может использоваться в нескольких атрибутах сущности. Названия поля, к которому привязан маппинг, тоже можно использовать при именовании файла. Например, в конце названия файла добавляется название поля, к которому загружен файл:
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
namer:
translit: ~
propertyPostfix:
params: { use_field_name : true }Замена подстрок в названии файла
Для того чтобы заменить в названии файла строки, можно воспользоваться rename. Это может быть полезным, например, если используется название поля (см. предыдущий пример) и в названии поля есть строка которую требуется заменить или убрать. В примере ниже подстрока File убирается из названия файла (в params указываются пары «заменяемая строка — замена»):
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
namer:
translit: ~
propertyPostfix:
params: { use_field_name : true }
replace:
params: { File : ~ }Создание поддиректорий для загружаемых файлов
Для того чтобы файлы не скапливались в одной директории, можно использовать функционал автоматического создания поддиректорий для файлов (directory namer)
Создание поддиректорий на основе даты
Для загружаемых файлов могут создаваться поддиректории, названия которых основаны на значения поля сущности, имеющего тип date. В примере ниже создаются директории на основе значения поля createdAt, глубина создания — месяц (варианты year, month, day). Т.е. файл 123.jpg загруженный к сущности, дата создания которой 2013-01-01 будет размещен в поддиректории 2013/01/123.jpg.
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
directory_namer:
date:
params: { field : createdAt, depth : month }Создание поддиректории, на основе значения атрибута сущности
Для название поддиректории можно использовать значение атрибута сущности. Например идентификатор:
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
directory_namer:
property:
params: { field : "id"}Создание поддиректорий на основе названия поля, в котором используется маппинг
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
directory_namer:
property:
params: { use_field_name : true }Создание поддиректорий на основе названия класса сущности, в которой используется маппинг
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
directory_namer:
entityName: ~Комбинирование нескольких directory namer’ов
В конфигурации маппинга можнжно указать несколько directory namer’ов, они будут применяться по цепочке, создавая путь к поддиректории. Например можно использовать комбинацию из названия класса сущности и названия поля:
# app/config/config.yml
iphp_file_store:
mappings:
some_entity:
directory_namer:
entityName: ~
property:
params: { use_field_name : true }
Подскажите, пожалуйста, как можно изменить шаблон отображения формы загрузки файла?
Шаблон находится в файле Iphp/FileStoreBundle/Resources/views/Form/fields.htmlo.twig. Переопределение шаблонов производится как описано здесь http://symfony.com/doc/current/cookbook/bundles/inheritance.html#overriding-resources-templates-routing-validation-etc
Создал бандл https://gist.github.com/anonymous/5677901 , зарегистрировал его, создал шаблон Acme/FileStoreBundle/Resources/views/Form/fields.html.twig , но все равно он не переопределяется, может еще какие действия нужно совершить? Кеш чистил.
Спасибо за ответ, все переопределилось, видимо я плохо смотрел)
После последнего обновления перестала работать функция удаления загруженного файла, не подскажете в чём проблема?
Исправлено
Спасибо, всё работает отлично)
Подскажите, пожалуйста. Я пытаюсь связать SonataAdminBundle, и при использовании OneToMany(sonata_type_collection) получаю странное поведение. Точнее при добавлении в список, заменяется шаблон на стандартный.
При добавлении записи в sonata_type_collection пропадает отображение загруженных картинок? Да, из-за особенностей реализации sonata_type_collection есть такая проблема, пока не решена.
После создания новой записи в sonata_type_collection отображение производится уже правильно.
Так и не найдено решение этой проблемы?
Спасибо за ваш блог, очень помог в изучении предмета.
Здравствуйте.
Подскажите, пожалуйста, в чем ошибка, если файл не выгружается, а в таблицу БД в соответствующее поле записывается только одно значение массива: s:14:»filename.jpg»;
И, соответственно, при отображении Symfony выдает ошибку: Expected argument of type «object, array or empty», «string» given.
Делал почти то же самое в админской части (SonataAdminBundle) — там все работает прекрасно.
В пользовательской части (FOSUserBundle и SonataUserBundle) пытаюсь в форму редактирования профайла добавить дополнительное поле для выгрузки аватара.
Кнопка выбора файла есть, но картинка не выгружается.
#config.yml
iphp_file_store:
mappings:
user:
upload_dir: %kernel.root_dir%/../www/images/user
upload_path: /images/user
overwrite_duplicates: true
namer:
property:
params: { field : username }
#vendor\sonata-project\Sonata\UserBundle\Form\Type\ProfileType.php
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(‘avatar’, ‘iphp_file’, array(‘label’ => ‘Аватар’, ‘required’ => false))
->add(‘firstname’, null, array(‘label’ => ‘Имя’, ‘required’ => false))
->add(‘lastname’, null, array(‘label’ => ‘Фамилия’, ‘required’ => false))
…
;
}
#vendor\sonata-project\Sonata\UserBundle\Resources\views\Profile\edit_profile.html.twig
…
div class=»control-group»>
{{ form_label(form.avatar, ‘Аватар’, {‘attr’: {‘class’: ‘control-label’}}) }}
{{ form_widget(form.avatar) }}
{{ form_label(form.firstname, ‘label_profile_firstname’, {‘attr’: {‘class’: ‘control-label’}}) }}
{{ form_widget(form.firstname) }}
{{ form_label(form.lastname, ‘label_profile_lastname’, {‘attr’: {‘class’: ‘control-label’}}) }}
{{ form_widget(form.lastname) }}
…
{{ form_rest(form) }}
…
Спасибо!
Здравствуйте.
Вопрос решился очень просто:
(Sonata\UserBundle\Resources\views\Profile\edit_profile.html.twig)
Добавлением в тег атрибута enctype=»multipart/form-data».
При загрузке изображения с пробелами в названии имени, картинка загружается но не доступна. Как можно сделать чтобы имя загружаемого файла сменялось на hash от имени?
По умолчанию название файла транслитерируется и пробел заменяется на — (тире). Приведите пример своего файла конфигурации маппинга.
Стандартного именователя на основе хэш в поставке нет, можно написать самостоятельно расширив класс Iphp\FileStoreBundle\Naming\DefaultNamer
Создал сущность:
<?php
namespace Acme\MyBundle\Entity;
use Iphp\FileStoreBundle\Mapping\Annotation as FileStore;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="photo")
* @FileStore\Uploadable
*/
class Photo
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $title;
/**
* @Assert\File( maxSize="20M")
* @FileStore\UploadableField(mapping="photo")
* @ORM\Column(type="array")
*/
private $photo;
Создал класс класс для админки PhotoAdmin.php:
addIdentifier(‘title’);
}
protected function configureFormFields(FormMapper $formMapper)
{
return $formMapper->add(‘title’)->add(‘photo’, ‘iphp_file’);
}
}
Cоздал контроллер для админки PhotoAdminController.php (пустой):
<?php
namespace Acme\MyBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController;
class PhotoAdminController extends CRUDController
{
}
Создал сервис в конфиге:
acme_my.admin.photo:
class: Acme\MyBundle\Admin\PhotoAdmin
arguments: [~, Acme\MyBundle\Entity\Photo, AcmeMyBundle:PhotoAdmin]
tags:
— {name: sonata.admin, manager_type: orm, group: Основные, label: Фотки}
В админке появилась страница редактирования сущности. Добавил фотку, нажал сохранить. Выдало ошибку.
"На веб-сайте произошла ошибка при получении http://my/app_dev.php/admin/acme/my/photo/create?uniqid=s521a72a9b7071."
Объект сохранился, но без добавленной фотки.
Возможно проблема в размере файла и лимите на размер загружаемых файлов со стороны веб-сервера / PHP
Добрый день, подскажите, как задать размер высоту и ширину картинки ?
Картинки, выводящейся при использовании типа формы iphp_file ? Нужно переопределить шаблон IphpFileStoreBundle/Resources/views/fields.html.twig
Здравствуйте.
Подскажите, пожалуйста, решение.
К форме редактирования профиля пользователя (FOSUserBundle) добавил поле portret для выгрузки фотографии (IphpFileStoreBundle). В одной единой форме все прекрасно работает. Но нужно отделить выгрузку портрета от редактирования остальных данных. Отдельно от основной формы выгрузка работает, а первоначальная форма редактирования данных пользователя (уже без поля для портрета) дает ошибку:
«Expected argument of type string, array given».
…vendor\symfony\symfony\src\Symfony\Component\Validator\Constraints\FileValidator.php at line 93
Если в описании сущности убрать из описания свойства portret аннотацию @Assert\File( maxSize=»5M»), то прекрасно работает.
Попытки в форме «отключить» обновление свойства не помогают.
Ни ‘mapped’ => false, ни remove(‘portret’).
Спасибо.
Добрый день!
подскажите пожалуйста как можно перед сохранением картинки (то есть до отправки формы ) изменить название оригинального названия файла на время добавление в секундах и под таким именем сохранить в каталог!
Есть ли пример аннотации в yml?
Файлы загружаются, но как сохранить имена файлов ? У меня в сущности было свойство
/**
* @var string
* @ORM\Column(name=»tag», type=»string», length=255)
*/
private $image;
теперь /**
* @Assert\File( maxSize=»20M»)
* @FileStore\UploadableField(mapping=»image»)
**/
private $image;
Соответственно в бд теперь названия файла не хранится. как исправить?
в $image хранится массив с данными файла