Not sure on if the title makes sense, but will try explain with an example:
Say for instance I have the following Entities:
#[ORM\Entity(repositoryClass: Post::class)]
class Post
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $body = null;
#[ORM\ManyToOne(inversedBy: 'post')]
private ?User $user = null;
}
And
#[ORM\Entity(repositoryClass: User::class)]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column(type: Types::BLOB, nullable: true)]
private $summary = null;
#[ORM\OneToMany(mappedBy: 'posts', targetEntity: Post::class)]
private Collection $posts;
}
The join is a OneToMany where each user can have many posts and a each post can have only one user.
User::$summary is a blob in the database, so I have a Stream Normalizer to convert the PHP Stream Resource to a string when being normalized through the json response. This just checks if the $data being asked to be normalized is a Stream Resource or not by calling is_resource($data). If true, it can be normalized by this class.
class StreamNormalizer implements NormalizerInterface
{
public function __construct(
) {
}
public function normalize( $topic, string $format = null, array $context = [])
{
$data = stream_get_contents($topic);
return $data;
}
public function supportsNormalization($data, string $format = null, array $context = []): bool
{
return is_resource($data);
}
}
And the route in the Post controller I use to fetch the posts data is:
#[Route('/feed', name: 'app_post_feed', methods: 'GET')]
public function feed(EntityManagerInterface $entityManager): Response
{
$posts = $entityManager->getRepository(Post::class)->findAll();
return $this->json($posts);
}
This works as expected, except when a user has posted more than one post, then all further occurrences of that user object has the $summary empty:
[
{
"id": 1,
"user": {
"id": 1,
"summary": "This is a summary",
"email": "test@example.com",
"userIdentifier": "test@example.com",
"verified": false
}
},
{
"id": 2,
"user": {
"id": 1,
"summary": "",
"email": "test@example.com",
"userIdentifier": "test@example.com",
"verified": false
}
}
}
As you can see, I have 2 posts, both of the same User. BUT only the summary for the user of the first post is set, the second post has its user missing its summary value, even though they are the same user!
I am guessing it is maybe intentional behaviour not to re-normalize an object more than once for performance? But I would have expected the data in all occurrences to be the same for the same entity?
I tried adjusting my Normalizer in case it is trying to re-normalize an already normalized object, but that made no difference:
class StreamNormalizer implements NormalizerInterface
{
public function __construct(
) {
}
public function normalize( $topic, string $format = null, array $context = [])
{
$data = is_resource($topic) ? stream_get_contents($topic) : $topic;
return $data;
}
public function supportsNormalization($data, string $format = null, array $context = []): bool
{
return is_resource($data);
}
}
as per @Dylan suggestion, adjusting the getter to the following still did not resolve the issue:
public function getSummary()
{
return is_resource($this->summary) ? stream_get_contents($this->summary) : $this->summary;
}
I have searched around but as I am not sure on how to word this issue, I did not find anything similar to the issue I am facing. If this has already been answered somewhere, a link to the answer will be appreciated!
If anyone knows how to adjust this so it returns the same result for all occurrences of repeated objects that would be helpful!
Answered on another site: https://github.com/symfony/symfony/discussions/51430
Turns out I needed to rewind the stream after getting its contents, otherwise the pointer was (I assume?) still at the end of the stream and so no value was after that.
My new StreamNormalizer:
class StreamNormalizer implements NormalizerInterface
{
public function __construct(
) {
}
public function normalize( $topic, string $format = null, array $context = [])
{
//get stream contents
$data = stream_get_contents($topic);
//rewind stream to revert the pointer to the start when this stream is read again
rewind($topic);
return $data;
}
public function supportsNormalization($data, string $format = null, array $context = []): bool
{
return is_resource($data);
}
}