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

@VirtualProperty makes collection null #596

Closed
aliemre opened this issue Jul 25, 2017 · 24 comments
Closed

@VirtualProperty makes collection null #596

aliemre opened this issue Jul 25, 2017 · 24 comments
Labels

Comments

@aliemre
Copy link

aliemre commented Jul 25, 2017

Hi.
I use @VirtualProperty annotation within a method that implements Criteria to filter down results. However, the collection gets null and Symfony throws a FatalErrorException:

Error: Call to a member function matching() on null

Without VirtualProperty, Criteria works normal. Could you help? Thanks in advance.

    /**
     * @var \Doctrine\Common\Collections\Collection
     * @Serializer\Exclude()
     */
    private $bestThings;

    /**
     * @Serializer\VirtualProperty()
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getBestThings()
    {
        $criteria = Criteria::create();
        $criteria->where(Criteria::expr()->eq('visible', true));
        return $this->bestThings->matching($criteria);
    }

Stack Trace: (1 Line only)

[1] Symfony\Component\Debug\Exception\FatalErrorException: Error: Call to a member function matching() on null
    at n/a
        in /home/webridge/public_html/demo/igg/src/System/RestaurantBundle/Entity/Restaurant.php line 1326

Restaurant.php
https://gist.github.com/aliemre/8cc4afd39bf612031243f4352a96d8d3

Running Symfony 2.8 and JmsSerializerBundle 1.1.

@goetas
Copy link
Collaborator

goetas commented Jul 25, 2017

Can you post the error trace?

@aliemre
Copy link
Author

aliemre commented Jul 25, 2017

@goetas Updated the post.

@goetas
Copy link
Collaborator

goetas commented Jul 25, 2017

$this->bestThings is null... you have to instantiate it in the constructor.

is this a deserialized object that gets re-serialized?

  • if the object was instantiated manually (and the constructor was called), the behavior you described should be impossible.
  • if the object was instantiated by doctrine, the bestThings should be a PersistentCollection injected by doctrine (depending on your metadata)

@aliemre
Copy link
Author

aliemre commented Jul 25, 2017

This is a normal object. After instantiated by doctrine, it is serialized. BestThings is a one-to-many association. So, I think it should be an ArrayCollection. I have also initiated in the constructor as ArrayCollection.

Iniated ArrayCollection with real objects (by doctrine) is serialized successfully without using VirtualProperty. I get JSON array after request. My fault if I don't understand you @goetas .

screen shot 2017-07-25 at 18 55 22

@goetas
Copy link
Collaborator

goetas commented Jul 25, 2017

are you using it with the symfony bundle, or using is as standalone library?
saw now, you are using the bundle...

@goetas
Copy link
Collaborator

goetas commented Jul 25, 2017

hmm... weird... you can try to see if the object is a proxy... maybe the serialized did not trigger the proxy initialization... but still weird to me..

@flohw
Copy link

flohw commented Sep 20, 2019

Hi. I hope to offend nobody to wake an old/stale issue. But I'm affraid to face the same problem without any solution/workaround found yet. The only difference is that the all the entitie's fields are null and not only the collection and as aliemre mentionned, my collection is not empty and none of the entities are null.

I pasted the stacktrace on pasatebin. This is an array of CommercialArea with a commercials ArrayCollection of AdminUsers with a roles ArrayCollection of Roles. The property role is not exposed directly but exposed with two virtual properties : getRoles (serialized as roleNames) wich returns an array of role names as string (this virtual property is typed as array. The second one is getRoleIds (serialized as rolesIds) wich returns an array of role ids as int (the virtual property is not typed). I use the ArrayCollection::map and toArray to transform my ArrayCollection to an array of string/int.

I use SF4.3 with jms serializer bundle 3.4.1 (jms serializer 3.2.0). This appens during a migration from SF3.4 with jms serializer bundle 2.4.4 (jms serializer 1.14.0) where I had no issues.
Indeed, it looks to appear on collections/ArrayCollection (wich are PersistentCollection when retrieved by doctrine automatically)
I tried to debug with xdebug but didn't understand very well how the entities are traversed. I looks to traverse the whole PersistentCollection preperties (which I don't care) and fails at some point but didn't understand how.

I hope we can find a solution as this is the only problem blocking me for the migration. I have only 6 virtual properties for now which are commented to work on other parts of the migration and it's a small amount of perperties reguarding the base code but still, this is a major issue for me.

Let me know if I can give more info to help.

@goetas
Copy link
Collaborator

goetas commented Sep 20, 2019

Context visitingStack not working well

Do you have some custom handlers/event-listeners ? this error seems caused by them.

Thanks for providing the detailed description for your problem, but unfortunately is still very specific and connected to your application... any chance to extract the issue and create a repository that reproduces the issue independently? (so I can debug it)

@flohw
Copy link

flohw commented Sep 20, 2019

I have listeners but not related to jms. Related to doctrine but not on the entities mentionned.

I will try to reproduce on a smaller app but it may take some time. As there will be only three entities, I bet this will work fine. 😈

@flohw
Copy link

flohw commented Sep 20, 2019

@goetas I managed to create a minimal project. I use the same dependencies (not all only the required ones), same configuration and serialization configuration. The only difference between the small project and mine is the mysql version (5.7 here, 5.6 on my project) but is not relevant here as I was able to reproduce !

Here is the project : https://github.com/flohw/jms_issue
You will need docker (or install the project with any other method but it ensure the same configuration). Then run make start to start the environment (php, mysql, nginx, phpmyadmin). And finally DUMP=dump.sql make db-reset to import data (anonymized but field which are null in my project are null here and those with data have data here)
The url to get the error: http://localhost/api/commercial-area there are no other route in the application.

Let me know if you can find something! Thanks

@goetas
Copy link
Collaborator

goetas commented Sep 20, 2019

Fixed in schmittjoh/serializer#1126

Was quite a challenge! Thanks to @flohw for the effort in isolating the issue in a reproducible project

@goetas goetas closed this as completed Sep 20, 2019
@flohw
Copy link

flohw commented Sep 20, 2019

Awesome! I didn't expect a fix so quickly!!!
Can I ask when a new release with the fix will be provided? Do I need to update the bundle with it's dependencies and it will update the jms/serializer dependency?

Thank you!

@goetas
Copy link
Collaborator

goetas commented Sep 20, 2019

updating jms/serializer should be enough, no need to update the bundle

@flohw
Copy link

flohw commented Sep 20, 2019

It works just fine when I installed jms/serializer and targeted dev-master. Will you release a new tag soon?

The same problem seems to appear with a serialization configuration a bit more complex:

App\Entity\SomeEntity:
    properties:
        marketCharges:
            expose: true
            serialized_name: marketCharges
            type: array<int, App\Entity\MarketCharge>
            accessor:
                getter: getMarketCharges

I think it's only due to the type info. I use this method to be able to enforce a key/value association on my array to use it on an angular app in front. The key is important as it represent an id to map on another variable used to display info.
I hope you will be able to reproduce wihtout another isolated project (or at least my previous one handles it)

@goetas
Copy link
Collaborator

goetas commented Sep 20, 2019

Will you release a new tag soon?

See https://github.com/schmittjoh/serializer/releases/tag/3.3.0

@goetas
Copy link
Collaborator

goetas commented Sep 20, 2019

Did not understand the second part of your message. Did schmittjoh/serializer#1126 solve your issue or not?

@flohw
Copy link

flohw commented Sep 20, 2019

Yes but not everywhere...
When I add the type configuration it didn't work. I have the same error message.

This both example work:

App\Entity\SomeEntity:
    properties:
        marketCharges:
            expose: true
            serialized_name: marketCharges
            accessor:
                getter: getMarketCharges
####
App\Entity\SomeEntity:
    virtual_properties:
        getMarketCharges:
            expose: true
            serialized_name: marketCharges

These both didn't:

App\Entity\SomeEntity:
    properties:
        marketCharges:
            expose: true
            serialized_name: marketCharges
            type: array<int, App\Entity\MarketCharge>
            accessor:
                getter: getMarketCharges
######
App\Entity\SomeEntity:
    virtual_properties:
        getMarketCharges:
            expose: true
            serialized_name: marketCharges
            type: array<int, App\Entity\MarketCharge>

@goetas
Copy link
Collaborator

goetas commented Sep 20, 2019

Can you share the code of getMarketCharges ?

@flohw
Copy link

flohw commented Sep 20, 2019

    public function getMarketCharges()
    {
        // Reindex collection by market id
        $charges = new ArrayCollection();
        $this->marketCharges->map(function (MarketCharge $marketCharge) use ($charges) {
            $charges->set($marketCharge->getMarketId(), $marketCharge);
        });

        return $charges;
    }

I think I can return a real php array instead of an ArrayCollection

@flohw
Copy link

flohw commented Sep 20, 2019

Ideed. The serialization works fine but I loss the indexing by $marketCharge->getMarketId() wich is important for the front for now.

@flohw
Copy link

flohw commented Sep 20, 2019

I have another part of the code where I return a new ArrayCollection too:

    public function getActivePrices()
    {
        if (null === $this->activePrices) {
            $criteria = Criteria::create()->where(Criteria::expr()->eq('isActive', true));

            $this->activePrices = $this->getPrices()->matching($criteria);
        }

        return $this->activePrices;
    }

Here the order doesn't matter but I use some ArrayCollection method on it. This method is a virtual property too and cause a problem when not commented.

@flohw
Copy link

flohw commented Sep 20, 2019

After some more testing I can confirm that returning a new ArrayCollection produces the error.

I hope you can find a solution for that too 🙂
I won't be able to do anything to help you more this weekend. I wish you a good one 😉

@goetas
Copy link
Collaborator

goetas commented Sep 20, 2019

If you are returning ArrayCollection or any other "doctrine" collection types, you have to use the right type hint (ArrayCollection in this case. See https://jmsyst.com/libs/serializer/master/reference/annotations#type)

@flohw
Copy link

flohw commented Sep 23, 2019

It works beautifully with type: ArrayCollection<int, App\Entity\Member\MarketCharge>.

Huge thank you again. I didn't expect a fix so quickly! 😀

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

No branches or pull requests

3 participants