0

I've already searched on StackOverflow and read the corresponding chapters of the Doctrine documentation (http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/index.html) but can't find what I'm looking for.

I'm working on an API made with Symfony v2.8, using Doctrine MongoDB v1.1 and a MongoDB v3.0.14 database.

I have two differents Documents: "Spot" and "Feature". A "Spot" contains a collection of document "Feature", with a ReferenceMany relation:

// Spot document declaration

/**
 * @var \Transversal\SessionBundle\Document\Feature
 * @MongoDB\ReferenceMany(strategy="set", targetDocument="Transversal\SessionBundle\Document\Feature", sort={"uploadDate"="asc"})
 * @Serializer\Expose
 */
protected $features = array();

I'm working on the Spot creation route/controller, where I need to send the Spot name, description and the list of existing Features I want to add to the spot.

What I'm doing for now is sending the name, the description and an array of Feature ids in the body of the request. Then, in the controller, I loop through this array, and for each id I:

  • Get an instance of the Feature from the db via its id (so it's one db request per iteration)
  • Add it to the Spot object via my $spot->addFeature() method

Then I persist and flush to save the newly created spot. Here is the code of my controller method (I modified the code to make it more readable):

 * @Rest\Post("")
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   * @throws BosHttpException
   */
  public function createAction()
  {
    $spot = new Spot();
    $request = $this->getCurrentRequest();

    // retrieve the name and description for the new spot here
    $form = $this->createForm(SpotType::class, $spot);
    $form->handleRequest($request);

    $content = $request->getContent();
    $params = "";
    if (!empty($content)) {
      $params = json_decode($content, true);
    }

    $document_manager = $this->getDocumentManager();
    $featureIds = $params['featureIds'];
    foreach ($featuresIds as $featureId) {
      $feature = $document_manager->find('Transversal\SessionBundle\Document\Feature', $featureId);
      $spot->addFeature($feature);
    }

    $document_manager = $this->getDocumentManager();
    $document_manager->persist($spot);
    $document_manager->flush();

    return $this->getOutputView("Spot creation - succeed", Codes::HTTP_CREATED, $this->formatResponse(null, true));

  }

Here is the code of addFeature() in the Spot.php file:

/**
 * Add feature
 *
 * @param \Transversal\SessionBundle\Document\Feature $feature
 */
public function addFeature(\Transversal\SessionBundle\Document\Feature $feature)
{
    $this->features[] = $feature;
}

It means that if I have an array of 20 Feature's id, my foreach loop will request 20 times my db and I know it's not a viable solution (I know I probably can use a single request to get them all but this is not what I'm looking for).

Is there a way to assign the Features to the Spot without having to generate instances of them and requesting my db, knowing that there are references ?

Thanks in advance for your help!

Enzo
  • 5
  • 1
  • 3
  • Not an answer to your question, but a tip to improve your code. You don't need to do `$request = $this->getCurrentRequest();` because you can inject into method signature: `public function createAction(Request $request)`. – Francesco Abeni Jul 24 '17 at 20:38
  • @FrancescoAbeni Thank you for the tip! – Enzo Jul 25 '17 at 09:10

1 Answers1

0

This is an example of how you could use @ParamConverter to pass just the id of the $feature and not the object

use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

/**
 * @ParamConverter("feature", class="YourBundle:Feature")
 */
public function addFeature(\Transversal\SessionBundle\Document\Feature $feature){
  $this->features[] = $feature;
}

Which means that if you let the code like this

foreach($featuresIds as $featureId) {  
  $spot->addFeature($featureId);
}

will work as well because doctrine should recognize that the id that you are passing as parameter is the id of the 'Feature' entity. Anyway the ORM is doing a query to get the object too, you can try and compare times.

Migdress
  • 158
  • 3
  • 13
  • Thanks for your reply. Unfortunately, if I pass the id as parameter instead of an instance of Feature, I get an exception saying `"Argument 1 passed to Transversal\\SpotBundle\\Document\\Spot::addFeature() must be an instance of Transversal\\SessionBundle\\Document\\Feature, string given"`; this solution isn't working for me. – Enzo Jul 24 '17 at 20:27
  • I just edited the post, see this [@ParamConverter documentation](https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html) – Migdress Jul 25 '17 at 12:51