JMSSerializer. Пишем свой обработчик

Написана 22 Августа, 2013 в 16:44. Автор: borN_free   |   Теги: symfony, jmsserializer, handler Комментарии 8

  • Задача: написать API, отдающее продукты с вложенными картинками (URLs на них) в виде JSON
  • Используется: Symfony2, FOSRestBundle + NelmioApiDocBundle
  • Сериализация через JMSSerializer. В ответе сериализуются сущности (Doctrine Entity).

Необходимость в написании своего обработчика появилась из-за использования Sonata Media Bundle, а именно:

при сериализации сущности, вложенная в продукт сущность картинки (Sonata\Media) сериализуется без URL. Кто сталкивался с этим бандлом, тому известно, что сущность не имеет метода получения URL, а необходимо использовать MediaManager.

Код примерно такой:

$mediaService = $this->container->get('sonata.media.pool');
$provider = $mediaService->getProvider($media->getProviderName());
$format = $provider->getFormatName($media, 'small');
$url = $provider->generatePublicUrl($media, $format);

Очень много действий, да еще и Service Container надо использовать. В Entity это никак не запихнуть в какой-нибудь метод ->getUrl(). Вы ведь не используете контейнер в сущностях, да? :)

Итак, т.к. сериализацией занимается JMSSerializer бандл, которому "на вход" поступает наша сущность Product, мы попробуем написать свой обработчик, в который инъектируем сервисы, ну или сразу весь Service Container. И тогда сможем получить URL для каждой картинки (сущности).

Кода будет не мало, но мы ведь хотим получить ссылки на картинки.

Первым делом создадим сервис. Я предпочитаю YAML:

# App\TestBundle\Resources\config\services.yml
services:
    app_test.serializer.handler:
        class: App\TestBundle\Serializer\Handler
        tags:
            - { name: jms_serializer.subscribing_handler }
        arguments: ["@doctrine.orm.entity_manager", "@router", "@sonata.media.pool", ["@sonata.media.provider.image", "@sonata.media.provider.imagelink"]]

Сервис написали, теперь напишем сам обработчик:


/**
 * Custom serializer for SonataMediaBunlde media objects
 */
namespace App\TestBundle\Serializer;

use Application\Sonata\MediaBundle\Entity\Media;
use Doctrine\ORM\EntityManager;
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonSerializationVisitor;
use Sonata\MediaBundle\Model\MediaInterface;
use Sonata\MediaBundle\Provider\Pool;
use Symfony\Component\Routing\RouterInterface;

/**
 * Class Handler
 * @package TailoredGift\ApiBundle\Serializer
 */
class Handler implements SubscribingHandlerInterface
{
    /**
     * @var EntityManager
     */
    protected $em;

    /**
     * @var \Symfony\Component\Routing\RouterInterface
     */
    protected $router;

    /**
     * @var \Sonata\MediaBundle\Provider\Pool
     */
    protected $mediaService;

    /**
     * @var array
     */
    protected $serializeProviders;

    /**
     * @param EntityManager   $em
     * @param RouterInterface $router
     * @param Pool            $mediaService
     * @param array           $serializeProviders
     */
    public function __construct(
        EntityManager $em,
        RouterInterface $router,
        Pool $mediaService,
        array $serializeProviders = array()
    ) {
        $this->em = $em;
        $this->router = $router;
        $this->mediaService = $mediaService;
        $this->serializeProviders = $serializeProviders;
    }

    /**
     * @return array
     */
    public static function getSubscribingMethods()
    {
        return array(
            array(
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => 'json',
                'type' => 'Application\Sonata\MediaBundle\Entity\Media',
                'method' => 'serializeImageToJson',
            ),
        );
    }

    /**
     * @return \Sonata\MediaBundle\Provider\Pool
     */
    private function getMediaService()
    {
        return $this->mediaService;
    }

    /**
     * @param \Sonata\MediaBundle\Model\MediaInterface $media
     * @param string                                   $format
     *
     * @return string
     */
    private function path(MediaInterface $media, $format)
    {
        $provider = $this->getMediaService()
            ->getProvider($media->getProviderName());

        $format = $provider->getFormatName($media, $format);

        return $provider->generatePublicUrl($media, $format);
    }

    /**
     * Return if the provider is allowed to be serialized
     *
     * @param  string $name
     * @return bool
     */
    public function serializeProvider($name)
    {
        $providersNames = array_map(
            function ($v) {
                return $v->getName();
            },
            $this->serializeProviders
        );

        return in_array($name, $providersNames);
    }

    /**
     * Get formats for media
     *
     * @param  MediaInterface $media
     * @return array
     */
    public function getFormats(MediaInterface $media)
    {
        return $this->getMediaService()->getFormatNamesByContext($media->getContext());
    }

    /**
     * Get a url safe identifier
     *
     * @param $src
     * @return string
     */
    public function getUrlsafeId($src)
    {
        $src = substr($src, 1);

        return preg_replace('/[\/\.]/', '-', $src);
    }

    /**
     * Handles the serialization of an Image object
     *
     * @param  \JMS\Serializer\JsonSerializationVisitor $visitor
     * @param  \Sonata\MediaBundle\Model\MediaInterface $media
     * @param  array                                    $type
     * @param  Context                                  $context
     * @return array
     */
    public function serializeImageToJson(
        JsonSerializationVisitor $visitor,
        MediaInterface $media,
        array $type,
        Context $context
    ) {
        if (!$this->serializeProvider($media->getProviderName())) {
            return;
        }

        $formats = $context->attributes->get('image_formats')->getOrElse(array());
        $urls = new \stdClass();
        $requestContext = $this->router->getContext();
        $host = $requestContext->getScheme() . '://' . $requestContext->getHost();

        foreach ($formats as $format) {

            switch ($media->getProviderName()) {
                case 'sonata.media.provider.imagelink':
                    $urls->$format = $this->path($media, $format);
                    break;
                default:
                    $urls->$format = $host . $this->path($media, $format);
                    break;
            }
        }

        return array(
            'id' => $media->getId(),
            'width' => $media->getWidth(),
            'height' => $media->getHeight(),
            'size' => $media->getSize(),
            'urls' => $urls,
            'name' => $media->getName(),
            'enabled' => $media->getEnabled(),
            'provider_name' => $media->getProviderName(),
            'provider_status' => $media->getProviderStatus(),
            'provider_reference' => $media->getProviderReference(),
            'provider_metadata' => $media->getProviderMetadata(),
            'context' => $media->getContext(),
            'updated_at' => $media->getUpdatedAt(),
            'created_at' => $media->getCreatedAt(),
            'content_type' => $media->getContentType(),
            'context' => $media->getContext(),
        );
    }
}

На строках 69 и 70 мы определяем, что нужно обрабатывать особым образом сущность Media, и будет это делать метод serializeImageToJson().

Теперь далее сам метод: на строке 160 мы определяем форматы, они задаются из контроллера, код которого представлен чуть ниже. Ну дальше все очевидно, в методе path() мы получаем нужный сервис и генерируем ссылку. В итоге возвращаемый массив - это сериализованная особым образом (как нам нужно) сущность Media.

Код контроллера:

...
class ProductsController extends FOSRestController
{
    public function getProductsAction()
    {
        // ...
        $view = $this->view(
            array(
        // ...
                'products' => $products,
            ),
            200
        )->setFormat('json');
        
        $context = new \JMS\Serializer\SerializationContext();
        $context->setAttribute('image_formats', array('small'));
        $view->setSerializationContext($context);

        return $this->handleView($view);
    }
}

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

[JMSSerializer: how to write custom handler]

8 comments

-1 ответить
September 7, 2015 at 02:59 pm

add line to file \src\Application\Sonata\MediaBundle\Resources\config\serializer\Entity.Media.xml

    <virtual-property name="referenceFull" type="string" expose="true" since-version="1.0" groups="sonata_api_read,sonata_search" method="getReferenceFull" />

and code...

+1 ответить
March 7, 2017 at 05:20 pm

На сегодня появились аннотации, с помощью которых можно легко решить этот вопрос. Например, данный вопрос можно было решить с помощью

@JMS\Accessor(getter="someGetMethod", setter="someSetMethod")

И просто пишем эти методы. Т.е. можно не описывать полностью целый слушатель (лисеннер), а просто добавить аннотацию.

Возможно я ошибаюсь, но для меня работает. Читайте документацию: http://jmsyst.com/libs/serializer/master/reference/annotations#accessor

ответить
April 25, 2017 at 01:21 pm

А если в другом месте Media нужно serialize иначе?

ответить
August 15, 2017 at 03:40 am

The green stamp is a symbol of rolex daydate the top chronometer chronometer. Each Rolex watch is replica watches enclosed with this stamp, and is guaranteed for fake rolex uk five years worldwide. It is very competitive.

ответить
August 22, 2017 at 07:12 am

About half an hour before the Toys 4 Tots Run was motorcycle mirrors with indicators due to start Saturday, a tractor stopped south of the intersection of highway foot pegs for motorcycles Lyon County Road 9 and Minnesota Highway 19. The tractor was towing a trailer decked out biker gloves online with picnic tables and awnings. Once it pulled over by the Rolling Hills Wildlife Management Area, members of motorcycle wheel spokes suppliers the Kesteloot family settled in to watch and wait.

ответить
August 24, 2017 at 01:36 am

The world record holder for the most expensive [url="http://www.topgunreplicawatch.org.uk"]fake watches[/url] ever sold at auction isn’t a lavishly appointed timepiece encrusted in diamonds or encased in 24-karat gold. Instead, the title belongs to a relatively understated, stainless steel watch made by Patek Philippe in 1943.Vintage [url="http://www.rolexreplicaforsale.co.uk"]fake rolex watches[/url] sport watches in particular are especially sought after due to their relatively large sizes – perfect for today’s tastes – and because they are so easy to wear.En attendant, on peut découvrir un autre pan, méconnu, plus pointu, de ses trésors: les [url="http://www.repliquemontrespascher.com"]replique montre[/url] émaillées.The Bao Dai was first sold at Phillips in 2002 for what was at the time the most expensive [url="http://www.lightreplica.com"]fake rolex sale[/url] ever acquired at auction, according to the auction house. A private collector was winning bidder and it had stayed in private hands since.It girls everywhere are flipping the script and wearing the [url="http://www.quiltersquartersaz.com/sitemap.asp"]chanel replica bags[/url] snugly under the boob. We don’t know when and where this phenomenon actually started.

ответить
September 8, 2017 at 02:34 pm

Les replique montres Breitling sont des imitations exactes des originaux. Chaque pièce reçoit une attention particulière dans la fabrication et a inspecté attentivement avant d'être retiré de l'entrepôt.

Si vous voulez une replique montre qui vous donnera l'apparence quotidienne et la durabilité dont vous avez besoin, notre montre Replica Breitling est exactement ce que vous recherchez. Longtemps connu pour sa durabilité couplé à un look étonnant qui n'a jamais été hors de style, les montres Breitling sont parmi les plus convoités au monde

ответить
September 21, 2017 at 01:51 am

Released in 2010 as a special anniversary piece, the fake rolex watches Submariner ref. 116610LV has remained as one of the most coveted modern Submariners ever produced. The ceramic paired with the immortalized “Rolex Green” just pops and can pair with anything from a nice summer dress to a casual t-Shirt & jeans look.Actuellement, ce sont 4 modèles qui sont proposés: Apex, Sekel, Nord, et Carat, avec pour chaque modèle entre 4 et 9 déclinaisons, ce qui fait déjà pas mal de choix. De prime abord, difficile de dire qu’il s’agit d’une replique montre connectée.The market for bags has been evolving to a smaller handbag niche market, and as a result, companies are incentivized to embrace new technologies to upgrade their products. In the past decade, there has been an increase in online sales of chanel replica bags.A handbag is a must-have accessory for every modern woman and the 10 most louis vuitton replica always attract attention – even though there are precious few people that can afford them.Coca has been producing two collections per year since he joined the company in 2015, and says that the fact that mulberry replica handbags has honed skills at its Somerset plants was a key reason he accepted the job.

Оставьте свой комментарий:

Поля с * обязательны.