1

Let's say I've got an entity called User with some properties like firstName, lastName, age and collection of Posts.

Is there a way to load this User entity with the EntityManager and Find-method without loading all properties/collections?

I'm using a REST API to retrieve the User and I want to seperate the calls:

  1. api/user/1 - should retrieve the User object with only firstName, lastName and age.
  2. api/user/1/posts - should retrieve the User object with the properties and a collection of Posts.

I know I can use QueryBuilder and create seperate methods to retrieve the desired data, but I want to have it this way:

The scenario:

api/user/1

$userId = 1;
$user = getEntityManager()->find('entity\User', $userId);
return $user; // should only load the firstName, lastName, age properties.

api/user/1/posts

$userId = 1;
$user = getEntityManager()->find('entity\User', $userId);
return $user->getPosts(); // should load the firstName, lastName, age properties and the collection of posts.

I already tried 'Extra Lazy Load' functionality that is built-in Doctrine, without any result.

My code:

<?php
/**
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 * @ORM\Table(name = "USER")
 * 
 * @JMS\ExclusionPolicy("all")
 * @JMS\AccessorOrder("custom", custom = { "id", "firstName", "lastName" })
 */
class User {

    /**
     * @ORM\Id
     * @ORM\Column(name = "id", type = "integer", options={"unsigned"=true})
     * @ORM\GeneratedValue(strategy = "IDENTITY")
     * @JMS\SerializedName("id")
     * @JMS\Expose
     * @JMS\Type("integer")
     * @var int
     */
    private $id;

    /**
     * @ORM\Column(name = "firstName", type = "string", length = 255)
     * @JMS\SerializedName("firstName")
     * @JMS\Expose
     * @JMS\Type("string")
     * @var string
     */
    private $firstName;

    /**
     * @ORM\Column(name = "lastName", type = "string", length = 255)
     * @JMS\SerializedName("lastName")
     * @JMS\Expose
     * @JMS\Type("string")
     * @var string
     */
    private $lastName;

    /**
     * @ORM\OneToMany(targetEntity = "Post", mappedBy = "user", cascade = { "persist" }, fetch="LAZY")
     * @JMS\Expose
     * @var ArrayCollection<Post>
     */
    private $posts;

    public function getId() {
        return $this->id;
    }

    public function getFirstName() {
        return $this->firstName;
    }

    public function getLastName() {
        return $this->lastName;


    public function getPosts() {
        return $this->posts->toArray();
    }

}

The posts are always loaded after fetching the user object, even though I use fetch type lazy or extra_lazy.

Swag
  • 2,090
  • 9
  • 33
  • 63
  • as long as you have your entities defined with lazy loaded collections (e.g. posts in your user entity) and you're not accessing the posts of a user it's not loaded. so you'll have to provide more details, but we fought a lot to get it running, so I might be able to help. – LBA Mar 03 '16 at 15:16
  • @LBA added the user class! – Swag Mar 03 '16 at 22:08
  • Wild guess: Maybe lazy loading gets hindered by `$this->posts->toArray()`, (what's your use case for the transformation? I always keep them as `ArrayCollection`). Could you try with just returning the posts as `$this->posts` and make sure nothing access a post. – k0pernikus Mar 03 '16 at 22:19
  • @k0pernikus either removing toArray() or the getPosts() method doesn't work... I'm simply calling getEntityManager()->find('namespace', $id); and when I echo that in JSON it also shows the posts... – Swag Mar 03 '16 at 22:24
  • Are you JSON encoding the entire user object? It can be that the very serialization triggers the lazy load. – k0pernikus Mar 03 '16 at 22:27
  • @k0pernikus Yes, I'm using JMS Serializer to JSON encode the user object. Why would that trigger the lazy load? :O – Swag Mar 03 '16 at 22:28
  • Maybe [this helps](http://stackoverflow.com/a/11682928/457268). – k0pernikus Mar 03 '16 at 22:30
  • @k0pernikus Gonna check it! I'll update u soon! – Swag Mar 03 '16 at 22:36

2 Answers2

2

Why not using Serialization Groups for it?

You can annotate your group (e.g. {'default'} and {'collection'} or even more specific {'posts'}.

Then you simply set your Serialization Context in your Controller and only the Group-related properties will be serialized.

See as well documentation

LBA
  • 3,859
  • 2
  • 21
  • 60
  • Thanks for the recommendation. Definitely going to use that I guess, but I guess that still triggers the lazy load? – Swag Mar 04 '16 at 17:37
  • 1
    The solution given is the right way to perform different exclusion strategy depending on the action of the request. You should also look at the [`@MaxDepth`](http://jmsyst.com/libs/serializer/master/reference/annotations#maxdepth) to limit the depth of the entries of your Post collection. – chalasr Mar 05 '16 at 13:24
  • As long as you don't access the lazy-loadable attribute it will not be loaded. Using groups here with the JMS Serializer avoids calling unnecessary attributes and therefore their loading. – LBA Mar 07 '16 at 09:03
1

You should write proper dql/querybuilder query and select only required for You fields.

It's not safe to return all posts on API just with $user->getPosts() - you will need pagination here.

You can also use JMSSerializerBundle. It gives you option to define needed property for Entity.

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
Paweł Mikołajczuk
  • 3,692
  • 25
  • 32