Dennis Haarbrink Dennis Haarbrink - 4 months ago 81
PHP Question

How to handle recursive objects with JMS Serializer

I am trying to serialize and deserialize a Doctrine object graph.

The structure is pretty complex, but this example sums up my problem:

There is a

Company
entity with a OneToMany relationship to
Employee
.

The
Employee
entity has a ManyToOne relationship with the
Company
.

This is serialized as follows:

{
"company": {
"name": "MegaCorp",
"employees": [{
"name": "John Doe",
"company": null
}]
}
}


So it
null
s the reference to the
Employee
's parent
Company
. For serialization this is ok.
But now when I deserialize this json, I get a
null
Company
in the
Employee
object. What I want (and expect) is to get a correct reference to the parent
Company
.

Is this possible using JMS serializer and if so, how can it be done?

If it is not possible, what could be a good workaround? Remember that it is a large graph, I don't want to do it manually.

Answer

Unfortunately, when deserializing, there is no way for the serializer to know whether the objects are identical or actually the same object. Even if you could nest them recursively.

But, you can get the desired result when combining the @Accessor annotation with some business logic. So going off of your example:

class Company {
    /**
     * @Accessor(setter="addEmployees")
     */
    private $employees;

    public function addEmployee(Employee $employee)
    {
        if (!$this->employees->contains($employee)) {
            $this->employees[] = $employee;
            $employee->setCompany($this);
        }
    }

    public function addEmployees($employees)
    {
        foreach ($employees as $employee) {
            $this->addEmployee($employee);
        }
    }
}

class Employee {
    /**
     * @Accessor(setter="setCompany")
     */
    private $company;

    public setCompany(Company $company = null)
    {
        $this->company = $company;

        if ($company) {
            $company->addEmployee($this);
        }
    }
}

I think this is a more natural approach than using @PostDeserialize as you probably already have some of these methods in your code. Also, it ensures the contract in both ways, so if you were to start from Employee you'd get the same result.

Comments