Сериализация указанных полей с помощью JMSSerializerBundle

Я создаю REST API и хотел бы дать своим пользователям возможность выбирать, какие поля возвращать через параметр URL, например

/users?fields=username,email,address.city,address.country

Есть ли способ реализовать такую ​​функциональность с помощью JMSSerializerBundle?

// РЕДАКТИРОВАТЬ

Обратите внимание на встроенные коллекции


person user2394156    schedule 19.10.2016    source источник


Ответы (2)


Я не думаю, что это работа для JMSSerializer, по крайней мере, полностью. Вместо этого я бы сделал следующее:

// Do not serialize into JSON or XML, but to PHP array
$userSerialized = $jmsSerializer->toArray($user);

// Turn "username,email" into ['username' => 0, 'email' => 1, ... ]
$fields = array_flip(explode($request->query->get('fields')));

$userSerializedFiltered = array_intersect_key($userSerialized, $fields);

// Finally, put it into desired format, JSON for example:
$json = json_encode($userSerializedFiltered);

Другая идея:

Вы можете использовать частичные объекты Doctrine:

$user = $em->createQuery("select partial u.{" . $fields . "} from MyApp\Domain\User u")->getResult();
$serialized = $jmsSerializer->serialize($user, 'json');

Надеюсь это поможет...

person Jovan Perovic    schedule 19.10.2016
comment
Хотя, если вам нужно более общее решение для всех сущностей, это не сработает, так как вам нужно повторить логику... - person Jovan Perovic; 19.10.2016
comment
Я думал о чем-то подобном, но мне кажется неприятным загружать полностью сериализованные данные в память, а затем уменьшать их, а не просто получать уменьшенные данные. - person user2394156; 19.10.2016
comment
Ага, вот идея: JSMSerializer по умолчанию пропускает NULL значений. Если бы вы могли загрузить свою сущность как частичный объект, вы могли бы добиться именно этого. - person Jovan Perovic; 19.10.2016
comment
Этот подход кажется хакерским, НО... у него есть дополнительное преимущество, заключающееся в том, что он выберет меньше столбцов из базы данных и сэкономит немного памяти/ускорит запросы :-). Я подумаю об этом решении на минуту - person user2394156; 19.10.2016

EDIT: этот ответ охватывает только первоначальный вопрос, который не требует более глубоких уровней сериализации. Я все равно сохраню его, так как это может помочь другим, которые также борются с этим первоначальным вопросом.

Мы сделали то же самое довольно общим способом.

Мы расширили ViewHandler для чтения из текущего Request, если «поля» были прикреплены в качестве параметра, и добавили ExclusionStrategy в Контекст сериализации.

Стоит отметить, что этот подход работает с FOS Rest Bundle 1.7.7 (мы пока не перешли на новейшую версию JMS), Symfony > 2.8 и JMSSerializerBundle 1.1.0, но миграция не должна быть слишком сложной. этот подход к любой другой комбинации, а также.

class ViewHandler extends \FOS\RestBundle\View\ViewHandler
{
/**
 * Extends ViewHandler, adds the exclusion strategies FieldListExclusionStrategy to the SerializationContext.
 *
 * Reads Request Parameter "fields" (comma separated list) and parses it into an array. Does some clean-up on parameter
 */
protected function getSerializationContext(View $view)
{
    $context = $view->getSerializationContext();
    $request = $this->container->get('request_stack')->getCurrentRequest();

    if ($request->isMethod('GET') && $request->query->has('fields')) {
        $fieldList = explode(',', $request->query->get('fields'));

        array_walk($fieldList, array(&$this, 'cleanString'));   //clean special characters except - and _
        $fieldList = array_filter($fieldList);                  // remove empty elements

        $context->addExclusionStrategy(new FieldListExclusionStrategy($fieldList));
    }

    $view->setSerializationContext($context);

    return parent::getSerializationContext($view);
}


/**
 * Helper to remove special characters from String, Compatible with array_walk
 *
 * @param string $string -
 * @param mixed  $key    -needed to be compatible with array_walk without raising a notice. (hands in 2 parameters)
 *
 * @return mixed
 */
private function cleanString(&$string, $key)
{
    $string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.
    $string = preg_replace('/[^A-Za-z0-9\-\_]/', '', $string); // Removes special chars.

    return preg_replace('/-+/', '-', $string); // Replaces multiple hyphens with single one.
}
} 

А это класс FieldListExclusionStrategy:

class FieldListExclusionStrategy implements ExclusionStrategyInterface
{
/**
 * @var array
 */
private $fields = array();
/**
 * @var int
 */
private $maxDepth;

/**
 * FieldListExclusionStrategy constructor.
 *
 * @param array $fields
 */
public function __construct(array $fields)
{
    $this->maxDepth = 1;
    $this->fields = $fields;
}

/**
 * Whether the class should be skipped.
 *
 * @param ClassMetadata $metadata
 * @param Context       $context
 * @return boolean
 */
public function shouldSkipClass(ClassMetadata $metadata, Context $context)
{
    return false;
}

/**
 * Whether the property should be skipped.
 *
 * @param PropertyMetadata $property
 * @param Context          $context
 *
 * @return boolean
 */
public function shouldSkipProperty(PropertyMetadata $property, Context $context)
{
    if (0 === count($this->fields)) {
        return false;
    }

    if ($context->getDepth() > $this->maxDepth) {
        return false;
    }

    $name = $property->serializedName ?: $property->name;

    return !in_array($name, $this->fields, true);
}
}
person LBA    schedule 19.10.2016
comment
Хороший прототип, но похоже, что он не будет работать со встроенными коллекциями. - person user2394156; 19.10.2016
comment
это прототип, который мы успешно используем уже более двух лет. извините, но ваш вопрос не требует коллекций - в настоящее время это действительно отсутствует в нашем решении (мы обрабатываем это по-разному), но это также должно быть возможно с этим подходом. - person LBA; 19.10.2016
comment
учитывая ваш вопрос, этот ответ совершенно прекрасен. как бы вы запросили коллекции в своем запросе? что-то вроде /users?fields=username,email,address.city или что вы имеете в виду? - person LBA; 19.10.2016
comment
это имеет большое значение здесь - но можете ли вы сказать мне, почему мой подход не должен быть осуществим здесь? и где ты останавливаешься? как насчет: /users?fields=имя пользователя,электронная почта,адрес.регион.страна.странаISO? - person LBA; 19.10.2016
comment
Это тоже проблема, о которой я думал. Я думаю, что должна быть какая-то карта, которую вы передаете при сериализации данных (или просто простой массив), поэтому сериализатор будет игнорировать поля, которые не указаны в карте. Я не знаю, как реализовать это с помощью JMSSerializerBundle, хотя я новичок в этом. Вероятно, вам нужно добавить стратегию исключения, но я не знаю, как заставить ее работать с картой. - person user2394156; 19.10.2016
comment
так что внимательно прочитайте мой код - вы можете расширить его таким образом. но я не рекомендую это делать, вам придется упомянуть все поля (которые могут получить довольно длинный список) как часть результата. - person LBA; 19.10.2016
comment
Допустим, у меня есть project.name, project.minorProgrammer.name и project.majorProgrammer.name. Как я узнаю, где я сейчас нахожусь в стратегии исключения? - person user2394156; 19.10.2016
comment
Например... как я узнаю, нахожусь ли я в данный момент внутри проекта или, например, внутри project.majorProgrammer? - person user2394156; 19.10.2016
comment
Последний комментарий: Да, это непросто. И именно поэтому я сказал, что не рекомендую ваш подход (и вы не найдете готовых решений). Мы используем группы и множество других вещей, подход «полей», который мы используем только для простых исключений. Кстати: расширять свой вопрос с каждым комментарием - не совсем честный способ. - person LBA; 19.10.2016
comment
Итак, вы рекомендуете включать/исключать свойства только основного объекта и всегда включать встроенные объекты в их форме по умолчанию? - person user2394156; 19.10.2016