IphpFileStoreBundle — symfony2 file upload

Бандл 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 }

23 комментария: IphpFileStoreBundle — symfony2 file upload

  1. Gwin Pin говорит:

    Подскажите, пожалуйста, как можно изменить шаблон отображения формы загрузки файла?

  2. run4life говорит:

    После последнего обновления перестала работать функция удаления загруженного файла, не подскажете в чём проблема?

  3. n0b0dy говорит:

    Подскажите, пожалуйста. Я пытаюсь связать SonataAdminBundle, и при использовании OneToMany(sonata_type_collection) получаю странное поведение. Точнее при добавлении в список, заменяется шаблон на стандартный.

    • vitiko говорит:

      При добавлении записи в sonata_type_collection пропадает отображение загруженных картинок? Да, из-за особенностей реализации sonata_type_collection есть такая проблема, пока не решена.
      После создания новой записи в sonata_type_collection отображение производится уже правильно.

      • Slider говорит:

        Так и не найдено решение этой проблемы?

        Спасибо за ваш блог, очень помог в изучении предмета.

  4. beginner говорит:

    Здравствуйте.
    Подскажите, пожалуйста, в чем ошибка, если файл не выгружается, а в таблицу БД в соответствующее поле записывается только одно значение массива: 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) }}

    Спасибо!

    • beginner говорит:

      Здравствуйте.
      Вопрос решился очень просто:
      (Sonata\UserBundle\Resources\views\Profile\edit_profile.html.twig)
      Добавлением в тег атрибута enctype=»multipart/form-data».

  5. Павел говорит:

    При загрузке изображения с пробелами в названии имени, картинка загружается но не доступна. Как можно сделать чтобы имя загружаемого файла сменялось на hash от имени?

    • vitiko говорит:

      По умолчанию название файла транслитерируется и пробел заменяется на — (тире). Приведите пример своего файла конфигурации маппинга.

      Стандартного именователя на основе хэш в поставке нет, можно написать самостоятельно расширив класс Iphp\FileStoreBundle\Naming\DefaultNamer

  6. fedor говорит:

    Создал сущность:

    <?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.&quot;

    Объект сохранился, но без добавленной фотки.

    • vitiko говорит:

      Возможно проблема в размере файла и лимите на размер загружаемых файлов со стороны веб-сервера / PHP

  7. alex говорит:

    Добрый день, подскажите, как задать размер высоту и ширину картинки ?

    • vitiko говорит:

      Картинки, выводящейся при использовании типа формы iphp_file ? Нужно переопределить шаблон IphpFileStoreBundle/Resources/views/fields.html.twig

  8. beginner говорит:

    Здравствуйте.
    Подскажите, пожалуйста, решение.
    К форме редактирования профиля пользователя (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’).
    Спасибо.

  9. Maks говорит:

    Добрый день!
    подскажите пожалуйста как можно перед сохранением картинки (то есть до отправки формы ) изменить название оригинального названия файла на время добавление в секундах и под таким именем сохранить в каталог!

  10. Gibz говорит:

    Есть ли пример аннотации в yml?

  11. Юрий говорит:

    Файлы загружаются, но как сохранить имена файлов ? У меня в сущности было свойство

    /**
    * @var string
    * @ORM\Column(name=»tag», type=»string», length=255)
    */
    private $image;

    теперь /**
    * @Assert\File( maxSize=»20M»)
    * @FileStore\UploadableField(mapping=»image»)
    **/
    private $image;
    Соответственно в бд теперь названия файла не хранится. как исправить?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">