Загрузка файлов в Symfony 2 / Doctrine ORM

Symfony 2 предоставляет значительное количество типов элементов форм для работы с датами, текстами, коллекциями и т.п. Однако для загрузки файлов существуют только стандартный элемент input type=upload и всю процедуру сохранения загруженного файла требуется программировать с 0

Существуют сторонние бандлы, такие как VichUploaderBundle, однако в нем не хватает возможности использования без дополнительного программирования в административном интерфейсе Sonata Admin Bundle., а также встроенных возможностей по переименованию загружаемых файлов и директорий в момент размещения файлов в файловой системе сервера. Чтобы добавить эти возможности был разработан IphpFileStoreBundle, описание которого представлено тут. Он создан на основе VichUploaderBundle (Doctrine ORM File Upload), но т.к. все переписано достаточно сильно данному бандлу дано собственное название.

Отличия IphpFileStoreBundle от VichUploaderBundle

1. Добавлен виджет формы iphp_file, который позволяет загрузить новый файл, удалить загруженный файл и выводит информацию о текущем файле. Данный виджет можно использовать при создании интерфейса редактирования в Sonata Admin Bundle.

2. В БД хранится не название файла, а массив с информацией о файле — директория хранения, путь на сайте, название файла, оригинальное название файла до переименования, размер файла. Для изображений — ширина и высота изображения. Соответственно тип поля в БД — array.

3. Полностью переработан интерфейс Namer’ов для файлов и директорий. Добавлен код переименования файлов в случае совпадения имен.

Пример использования IphpFileStoreBundle

В качестве примера рассмотрим создание раздела на сайте, в котором пользователи могут загружать свои фотографии, загруженные фотографии выводятся списком на странице сайта. Также требуется административный интерфейс для модерирования фотографий.

Инсталляция бандла

Получение кода бандла

Пример фрагмента composer.json для установки через composer

 
"require":{
   ...
   "iphp/filestore-bundle":"dev-master"
 }

Инициализация

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new Iphp\FileStoreBundle\IphpFileStoreBundle(),
    );
)

Конфигурация IphpFileStoreBundle

Для хранения фотографий нужно добавить в app/config/config.yml:

# app/config/config.yml
iphp_file_store:
    mappings:
       photo:
           upload_dir:  %kernel.root_dir%/../web/photo
           upload_path: /photo
           directory_namer:
               date:
                  params: { field : date, depth : month }
           namer: ~

Данная конфигурация нужна для того чтобы указать место хранения файлов (upload_dir), путь к этой директории на сайте (upload_path). Также, чтобы равномерно разбить загружаемые фотографии по директориям, указываем параметр directory_namer = date. Бандл поддерживает ряд встроенных именователей директорий, в которые загружаются файлы, один из них date. В данном случае атрибут date, который будет использоваться для создания директории файла, имеет тип datetime. Параметр depth определяет глубину, до которой будет создаваться поддиректории (month), доступные варианты — year, month, date. Именователь файлов (namer) не указан, по умолчанию используется именователь translit — названия файла транслитирируется в английские символы.
Чтобы имя файла оставалось без изменений — нужно указать namer: false.

Создание бандла IphpsandboxPhotoBundle

Весь код, связанный с загрузкой и отображением фотографий будем хранить в бандле IphpsandboxPhotoBundle. Создать бандл можно с помощью команды

php app/console generate:bundle

Создание сущности Doctrine ORM

В объекте Photo хранится название фотографии и информация о файле фотографии. В начале класса нужно добавить аннотацию @FileStore\Uploadable, а к полю, которое будет использоваться для загрузки файлов, аннотацию @FileStore\UploadableField(mapping=’photo’), c указанием маппинга файлов фотографий, определенного на предыдущем шаге в файле config.yml. Также для поля photo используется валидация на тип файла — изображение размером до 20 Мб.

<?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;
 
    /**
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
 
    /**
     * @param string $title
     * @return Photo
     */
    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }
 
    /**
     * @return string 
     */
    public function getTitle()
    {
        return $this->title;
    }
 
    /**
     * @param array $photo
     * @return Photo
     */
    public function setPhoto($photo)
    {
        $this->photo = $photo;
        return $this;
    }
 
    /**
     * @return array 
     */
    public function getPhoto()
    {
        return $this->photo;
    }
 
    /**
     * @param \Datetime $date
     */
    public function setDate($date)
    {
        $this->date = $date;
        return $this;
    }
 
    /**
     * @return \Datetime
     */
    public function getDate()
    {
        return $this->date;
    }
}

В xml-описании содержится описание связи PHP-класса и таблицы БД:

<?xml version="1.0" encoding="utf-8"?>
<!-- src/Iphpsandbox/PhotoBundle/Ressources/config/doctrine/Photo.orm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
    <entity name="Iphpsandbox\PhotoBundle\Entity\Photo">
        <id name="id" type="integer" column="id">
            <generator strategy="AUTO"/>
        </id>
        <field name="title" type="string" column="title" length="255"/>
         <field name="date" type="datetime" column="date"/>
        <field name="photo" type="array" column="photo"/>
   </entity>
</doctrine-mapping>

После создания PHP-класса сущности и XML-описания, нужно сгенерировать таблицу в БД командой:

php app/console doctrine:schema:update --force

Административный интерфейс

Подробно процесс создания административного интерфейса с помощью SonataAdminBundle описан тут. Для редактирования фотографий в административном интерфейс создаем класс

<?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');
    }
}

И создаем сервис:

<?xml version="1.0" ?>
<!-- src/Iphpsandbox/PhotoBundle/Ressources/config/services.xml -->
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="iphpsanbox.photo.admin.photo" class="Iphpsandbox\PhotoBundle\Admin\PhotoAdmin">
            <tag name="sonata.admin" manager_type="orm" group="Фотографии" label="Фотографии"/>
            <argument/>
            <argument>Iphpsandbox\PhotoBundle\Entity\Photo</argument>
            <argument>SonataAdminBundle:CRUD</argument>
        </service>
    </services>
</container>

После этого в административном интерфейсе появляется группа меню «Фотографии». Для добавления фотографии нужно выбрать пункт меню «Фотографии» и затем кнопку «Добавить новый». Затем вводится название фотографии, дата и файл с фотографией. После сохранения фотографии форма редактирования выглядит следующим образом:

Тип поля iphp_file отображает данные о загруженной фотографии — исходное название файла, текущее название файла, размер изображения и файла, предварительный просмотр изображения. Фотография загружена в директорию, согласно конфигурации, содержащую год и месяц даты фотографии — http://localhost/photo/2012/12/desert.jpg.

Пользовательский интерфейс

Теперь добавим в пользовательский интерфейс возможность загружать свои фотографии и просматривать загруженные фотографии. Для реализации можно использовать как стандартный подход Symfony 2, так и предлагаемый в Iphp Core Bundle подход с созданию пользовательского интерфейса на основе модулей, размещаемых в древовидной структуре сайта. В данной статье используем стандартный подход, описанный тут.

Шаг 1. Создание маршрута (Route)

В файл app/config/routing.yml нужно добавить

# app/config/routing.yml
iphpsandbox_photo:
    pattern:  /photo/
    defaults: { _controller: IphpsandboxPhotoBundle:Photo:index }

Шаг 2. Контроллер

В контроллере создается форма для редактирования сущности Photo, аналогичная форме в административном интерфейсе, за исключением того, что в пользовательском интерфейсе нужны возможности, предоставляемые типом iphp_file (вывод информации о загруженном файле), поэтому используется стандартный тип формы File.

<?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()
        ));
    }
}

Шаг 3. Шаблон

В шаблоне выводится список загруженных фотографий и отображается форма загрузки новой фотографии. В списке фотографий, в случае если высота фотографии больше 150 пикселей (информация о размере фотографии хранится БД, данные в БД заносятся автоматически при загрузке фотографии) — размер фотографии ограничивается высотой 150px. Это решение показано только для примера, для создания превью-картинок можно использовать LiipImagineBundle.

{# src/Iphpsandbox/PhotoBundle/Resources/views/Photo/index.html.twig #}
<h1>Фотографии</h1>
 
<h2>Загруженные фотографии</h2>
 
{% for photo in photos %}
<div>
<h3>{{ photo.title }}</h3>
<div>{{ photo.date | date ('Y.m.d') }}</div>
<a href="{{ photo.photo.path }}" target="_blank"><img src="{{ photo.photo.path }}" {{ photo.photo.height > 150 ? 'height="150"':''}}/></a>
</div>
{% endfor%}
 
<h2>Загрузить фото</h2>
 
<form action="./" enctype="multipart/form-data" method="post">
 
    {{ form_widget(uploadForm) }}
    <input type="submit" value="Загрузить фото"/>
</form>

В итоге пользовательский интерфейс выглядит вот так.

С минимальными затратами сделан административный и пользовательский интерфейс для работы с записями, к которым можно присоединять файлы. Бандл IphpFilestoreBundle в своем составе имеет несколько именователей для файлов и директорий, с помощью который можно создавать названия файлов на основе атрибутов записи.

Код бандла с данным примером можно увидеть здесь https://github.com/vitiko/IphpSandbox/tree/master/src/Iphpsandbox/PhotoBundle

Запись опубликована в рубрике Без рубрики. Добавьте в закладки постоянную ссылку.

4 комментария: Загрузка файлов в Symfony 2 / Doctrine ORM

  1. Иван говорит:

    Как через yml настроить а не через аннотации ? Все сделал стандартным CRUD генератором.

  2. Сергей говорит:

    - Installation request for iphp/filestore-bundle dev-master -> satisfiable by iphp/filestore-bundle[dev-master].
    - Conclusion: remove symfony/symfony v2.4.1

    на гитхаб отписался. сделайте пожалуйста.

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

Ваш 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="">