Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add metadata informations #637

Closed
matiux opened this issue Aug 29, 2016 · 32 comments
Closed

Add metadata informations #637

matiux opened this issue Aug 29, 2016 · 32 comments

Comments

@matiux
Copy link

matiux commented Aug 29, 2016

Hi,
I'm using jms serializer (bundle) in a symfony project.
For now, I'm serializing an array of object and it's all ok.

[
{"prop": "value"},
{"prop": "value"},
{"prop": "value"}
]

But I need to add information about the pagination (I fetch data with doctrine) to end up to have a json like this:

[
"meta": {"page": 2, "total": 3, "per_page": 2},
"results": [
   {"prop": "value"},
   {"prop": "value"},
   {"prop": "value"}
   ]
]

As you can see, I need to add the informations about total items, current pagination page and the number of the elements in the page.

I have created a PostSerializationSubscriber that implements JMS\Serializer\EventDispatcher\EventSubscriberInterface and I have implemented the getSubscribedEvents() method like this:

    public static function getSubscribedEvents()
    {
        return [
            [
                'event' => 'serializer.post_serialize',
                'method' => 'onPostSerialize'
            ],
        ];
    }

how can I edit my serialized object in onPostSerialize() method?

    public function onPostSerialize(ObjectEvent $postSerializeEvent)
    {
        $postSerializeEvent->getContext()->getVisitor()->addData('someKey','someValue');
    }

The code below, add 'someKey','someValue' for each elements... but it is not what I want

Thanks

@goetas
Copy link
Collaborator

goetas commented Aug 29, 2016

your data structure must be

{
"meta": {"page": 2, "total": 3, "per_page": 2},
"results": [
   {"prop": "value"},
   {"prop": "value"},
   {"prop": "value"}
   ]
}

(pay attention that I have used {} and not [])
and to do that you need an object as root object.
when you have a root object, you can easily add pagination information.

As example:

class PaginatedResult
{
  protected $items;
// @exclude
  protected $current_page;
// @exclude
  protected $page_nr;
}

and

public function onPostSerialize(ObjectEvent $postSerializeEvent)
    {
        $postSerializeEvent->getContext()->getVisitor()->addData('someKey','someValue');
    }

registered for PaginatedResult

@goetas goetas closed this as completed Aug 29, 2016
@matiux
Copy link
Author

matiux commented Aug 29, 2016

Hi @goetas, thanks!
Ok for the json, I had wrong the example.

However I don't still understand the way in which I can add PaginatedResult to my just serialized object.

Can you provide me some additional details?
And what is PaginatedResult? A plain object?

@goetas
Copy link
Collaborator

goetas commented Aug 29, 2016

@matiux
Copy link
Author

matiux commented Aug 29, 2016

But is there a way to achieve my goal without additional bundle?
I have seen in the first example that is called the method setRoot with an array so I have tried this:

$postSerializeEvent->getContext()->getVisitor()->setRoot(['tot' => 2]);

but doesn't work

@goetas
Copy link
Collaborator

goetas commented Aug 29, 2016

can you share the code used to generate the pagination result? (not the event listener)

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Thanks @goetas
I'm using doctrine Paginator. I have a Repository that returns Doctrine\ORM\Tools\Pagination\Paginator, and this is my serializer method:

class JmsRequestEmailSerializer extends JmsEntitySerializer implements RequestEmailSerializer
{
    public function serialize(Paginator $collection)
    {
        $serializedEntity = $this->serializer->serialize(
            $collection->getIterator()->getArrayCopy(),
            static::JSON_FORMAT
        );

        return $serializedEntity;
    }
}

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

how your code is able to obtain the number of pages ? you are passing to the serializer only the array of the current page... right?

@matiux
Copy link
Author

matiux commented Aug 30, 2016

The number of page can be obtained by dividing total items with the number of elements in page

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

so $collection->getIterator()->getArrayCopy() has the whole set... is not paginated

@matiux
Copy link
Author

matiux commented Aug 30, 2016

no no
has only paginated items because this is my query:

        $queryBuilder = $this->createQueryBuilder('s');
        $queryBuilder->setFirstResult(0)
            ->setMaxResults(40)
            ->orderBy('s.requestHeader.requestDate', 'ASC');

        $paginator = new Paginator($queryBuilder);

        return $paginator;

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

then you have to :

class JmsRequestEmailSerializer extends JmsEntitySerializer implements RequestEmailSerializer
{
    public function serialize(Paginator $collection)
    {
        $serializedEntity = $this->serializer->serialize(
            $collection,
            static::JSON_FORMAT
        );

        return $serializedEntity;
    }
}

and the handler now you can extract the page informations, because doing ->getIterator()->getArrayCopy() you loose all the info about the collection size

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Uhm ok..
so how can I use doctrine paginator to put meta information in the serialized json?

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Do I need to use KnpPaginatorBundle?

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

no, a custom listener on Paginator is enough

@matiux
Copy link
Author

matiux commented Aug 30, 2016

on Doctrine\ORM\Tools\Pagination\Paginator?

Can you help me with an example?

@matiux
Copy link
Author

matiux commented Aug 30, 2016

If I try to serialize a Paginator object,

    public function serialize(Paginator $collection)
    {
        $serializedEntity = $this->serializer->serialize(
            $collection,
            static::JSON_FORMAT
        );

I get this error:

Resources are not supported in serialized data. Path: Monolog\Handler\StreamHandler -> Symfony\Bridge\Monolog\Logger -> Symfony\Bridge\Doctrine\Logger\DbalLogger -> Doctrine\DBAL\Logging\LoggerChain -> Doctrine\DBAL\Configuration -> Doctrine\DBAL\Connection -> Doctrine\ORM\EntityManager -> Doctrine\ORM\Query -> Doctrine\ORM\Tools\Pagination\Paginator

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

you need a custom handler for it

class MyHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods()
    {
        return array(array(
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => 'json',
                'type' => 'Paginator',
                'method' => 'serialize'
            )
        );
    }

    public function serializeAnyType(JsonSerializartionVisigtor $visitor, Paginator $data, array $type, Context $context)
    {
        // serialize your object here
    }

}

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

of course you need the right paginator namespace and class name

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Ok, I have removed the listener to post.serialized event since it no longer need.
I have created a custom handler and I registered it

    jms_serializer.doctrine_paginator_handler:
        class: AppBundle\DomainModel\Stat\Service\Serializer\Jms\DoctrinePaginatorHandler
        tags:
            - { name: jms_serializer.subscribing_handler }

But I have still doubts about the creation of metadata and results fields

    public function serializeDoctrinePaginator(JsonSerializationVisitor $visitor, Paginator $paginator, array $type, Context $context)
    {
        $breakpoint = true;
    }

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

/src/JMS/Serializer/Handler/ArrayCollectionHandler.php is a good place to start with to see what it should be inside

    public function serializeCollection(JsonSerializationVisitor $visitor, Collection $collection, array $type, Context $context)
    {
        // We change the base type, and pass through possible parameters.
        $type['name'] = 'array';

        $p = new StaticPropertyMetadata(get_class($collection), 'page_nr', 5);
        $visitor->visitProperty($p, 5, $context);

        $p = new StaticPropertyMetadata(get_class($collection), 'total_pages', 99);
        $visitor->visitProperty($p, 5, $context);


        // serialize collection
        return $visitor->visitArray($collection->->getIterator()->toArray(), $type, $context);
    }

is just an example

@matiux
Copy link
Author

matiux commented Aug 30, 2016

There is something that doesn't work...

        $type['name'] = 'array';

        $totalPage = ceil($paginator->count() / 40);

        $p = new StaticPropertyMetadata(get_class($paginator), 'total_pages', $totalPage);
        $visitor->visitProperty($p, null, $context);

        $p = new StaticPropertyMetadata(get_class($paginator), 'total', $paginator->count());
        $visitor->visitProperty($p, null, $context);

        return $visitor->visitArray($paginator->getIterator()->getArrayCopy(), $type, $context);

return only the number 5, that is the total pages

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

Why null in $visitor->visitProperty($p, null, $context); ?

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

can you try to serialize $paginator->getIterator()->getArrayCopy() the result with StaticPropertyMetadata too?

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Whats is the second parameters of visitProperty() ?

visitProperty() method calls getValue() on StaticPropertyMetadata class but this is the implementation:

    public function getValue($obj)
    {
        return $this->value;
    }

$obj seems to be unused

About your last message, how can I serialize $paginator->getIterator()->getArrayCopy() with StaticPropertyMetadata?

thanks for your patience

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

        $totalPage = ceil($paginator->count() / 40);

        $p = new StaticPropertyMetadata(get_class($paginator), 'total_pages', $totalPage);
        $visitor->visitProperty($p,  $totalPage, $context);

        $p = new StaticPropertyMetadata(get_class($paginator), 'total', $paginator->count());
        $visitor->visitProperty($p,  $paginator->count(), $context);

        $p = new StaticPropertyMetadata(get_class($paginator), 'items', $paginator->getIterator()->getArrayCopy());
        $visitor->visitProperty($p,  $paginator->getIterator()->getArrayCopy() $context);

can this work?

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Nothing to do, continues to return "5" instead a serialized json object

$type['name'] = 'array';

$totalPage = ceil($paginator->count() / 40);

$p = new StaticPropertyMetadata(get_class($paginator), 'total_pages', $totalPage);
$visitor->visitProperty($p,  $totalPage, $context);

$p = new StaticPropertyMetadata(get_class($paginator), 'total', $paginator->count());
$visitor->visitProperty($p,  $paginator->count(), $context);

$p = new StaticPropertyMetadata(get_class($paginator), 'items', $paginator->getIterator()->getArrayCopy());
$visitor->visitProperty($p,  $paginator->getIterator()->getArrayCopy(), $context);

return $visitor->visitArray($paginator->getIterator()->getArrayCopy(), $type, $context);

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

in my example i have intentionally omitted return $visitor->visitArray($paginator->getIterator()->getArrayCopy(), $type, $context)

@matiux
Copy link
Author

matiux commented Aug 30, 2016

OK sorry, but the problem doesn't change.

I have remove the return line, but in this code

        $serializedEntity = $this->serializer->serialize(
            $collection,
            static::JSON_FORMAT
        );

$serializedEntity contains 5

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Obviously, serializer is an object of JMS\Serializer\Serializer type

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

@matiux I guess, you are italian, please write me over skype, my id is the same as my github username

@goetas
Copy link
Collaborator

goetas commented Aug 30, 2016

@matiux
Copy link
Author

matiux commented Aug 30, 2016

Thank @goetas finally works and this is the current implementation of the handler for Doctrine Paginator:

class DoctrinePaginatorHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods()
    {
        return [
            [
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format'    => 'json',
                'type'      => 'Doctrine\ORM\Tools\Pagination\Paginator',
                'method'    => 'serializeDoctrinePaginator',
            ]
        ];
    }

    public function serializeDoctrinePaginator(JsonSerializationVisitor $visitor, Paginator $paginator, array $type, Context $context)
    {
        $totalPage = ceil($paginator->count() / 40);

        $resultsType = array(
            'name' => 'array',
        );

        if (isset($type['params'])) {

            $resultsType['params'] = $type['params'];
        }

        $shouldSetRoot = null === $visitor->getRoot();

        $data = array(
            'page' => $paginator->getQuery()->getFirstResult() + 1,
            'total_page' => $totalPage,
            'total' => $paginator->count(),
            'results' => $visitor->getNavigator()->accept($paginator->getIterator()->getArrayCopy(), $resultsType, $context),
        );

        if ($shouldSetRoot) {
            $visitor->setRoot($data);
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants