drupal-graphql / graphql

GraphQL integration for Drupal 9/10
286 stars 202 forks source link

Setting a multiple Entity Reference field in a mutation #720

Closed rawxx closed 4 years ago

rawxx commented 5 years ago

Hello,

I have been using graphql beta5 for a while on my project because of the issue https://github.com/drupal-graphql/graphql/issues/610 and now I am trying to port it to the latest dev version. There's something I think I can't do anymore.

I have a content type called product_pack with a (possibly multiple) Entity Reference field fieldProducts, which lists contents of type product

With beta5 I used this mutation :

mutation ($input: NodeProductPackCreateInput!) {
  createNodeProductPack(input: $input) {
    entity {
      ... on NodeProductPack {
        fieldProducts {
          entity {
            entityId
          }
        }
      }
    }
  }
}

with the following variable, to reference two existing nodes at the same time :

{
  "input": {
    "fieldProducts": [
      {"targetId": "1001"},
      {"targetId": "1002"}
    ],
    "title": "Pack test"
  }
}

and it worked well

Now, with the latest dev version 8.x-3.x, the same mutation with the same variables will return a new NodeProductPack with empty field fieldProducts. The syntax of the mutation and the query seems to fit the Schema, and the Explorer doesn't raise any syntax issue.

Is there another way of setting multiple reference fields, or has it disappeared since the use of webonyx ?

Thanks again for this great tool

d1urno commented 5 years ago

Have you got around this somehow? I'm having the same issue here

rawxx commented 5 years ago

Hi @d1urno , I have had to create my own mutation for this particular case (only case in my application). See the code below.

In this case, I am able to ADD (not remove, but this was on purpose) several NodeProduct entities in a NodeProductPack's field_products. The variables are the NodeProductPack's nid and an array of the NodeProducts' nids

<?php

namespace Drupal\my_module\Plugin\GraphQL\Mutations;

use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\graphql\GraphQL\Execution\ResolveContext;
use Drupal\graphql_core\GraphQL\EntityCrudOutputWrapper;
use Drupal\graphql\Plugin\GraphQL\Mutations\MutationPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use GraphQL\Type\Definition\ResolveInfo;

/**
 *
 * @GraphQLMutation(
 *   id = "add_products_to_pack",
 *   secure = true,
 *   name = "addProductsToPack",
 *   type = "EntityCrudOutput",
 *   arguments = {
 *     "packId" = {
 *       "type" = "String",
 *       "nullable" = false
 *     },
 *     "productIds" = {
 *       "type" = "[String]",
 *       "nullable" = false
 *     }
 *   }
 * )
 */
class AddProductsToPack extends MutationPluginBase implements ContainerFactoryPluginInterface  {

  use StringTranslationTrait;

  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
    return new static(
        $configuration, $pluginId, $pluginDefinition, $container->get('entity_type.manager'), $container->get('renderer')
    );
  }

  /**
   * AddProductsToPack constructor.
   *
   * @param array $configuration
   *   The plugin configuration array.
   * @param string $pluginId
   *   The plugin id.
   * @param mixed $pluginDefinition
   *   The plugin definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager service.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   */
  public function __construct(array $configuration, $pluginId, $pluginDefinition, EntityTypeManagerInterface $entityTypeManager, RendererInterface $renderer) {
    parent::__construct($configuration, $pluginId, $pluginDefinition);
    $this->entityTypeManager = $entityTypeManager;
    $this->renderer = $renderer;
  }

  public function resolve($value, array $args, ResolveContext $context, ResolveInfo $info) {
    // There are cases where the Drupal entity API calls emit the cache metadata
    // in the current render context. In such cases
    // EarlyRenderingControllerWrapperSubscriber throws the leaked cache
    // metadata exception. To avoid this, wrap the execution in its own render
    // context.
    return $this->renderer->executeInRenderContext(new RenderContext(), function () use ($value, $args, $context, $info) {

          $storage = \Drupal::entityTypeManager()->getStorage('node');

          /** @var \Drupal\node\Entity\Node $pack */
          if (!$pack = $storage->load($args['packId'])) {
            return new EntityCrudOutputWrapper(NULL, NULL, [
              $this->t('The requested Pack could not be loaded : @nid.', ['@nid' => $args['packId']]),
            ]);
          } else {

            if (!$pack->bundle() === 'product_pack') {
              return new EntityCrudOutputWrapper(NULL, NULL, [
                $this->t('The requested Node is not of the expected type product_pack. @bundle found instead', ['@bundle' => $pack->bundle()]),
              ]);
            } else {

              $pids = [];
              foreach ($pack->field_products->getValue() as $val) {
                $pids[] = $val['target_id'];
              }

              foreach ($args['productIds'] as $nid) {
                //we only ADD Products, those already in the Pack are kept
                if (!in_array($nid, $pids)) {

                  /** @var \Drupal\node\Entity\Node $product */
                  if (!$product = $storage->load($nid)) {
                    return new EntityCrudOutputWrapper(NULL, NULL, [
                      $this->t('The requested Product could not be loaded : @nid.', ['@nid' => $nid]),
                    ]);
                  } else {

                    if (!$product->bundle() === 'product') {
                      return new EntityCrudOutputWrapper(NULL, NULL, [
                        $this->t('The requested Node is not of the expected type product. @bundle found instead', ['@bundle' => $product->bundle()]),
                      ]);
                    } else {

                      if (!product == $pack->field_products->appendItem($product)) {
                        return new EntityCrudOutputWrapper(NULL, NULL, [
                          $this->t('Unable to add product @pid to product_pack @ppid', ['@pid' => $product->id(), '@ppid' => $pack->id()]),
                        ]);
                      } else {

                        $updated = true;
                      }
                    }
                  }
                }
              }
            }
          }

          if ($updated) {
            $pack->save();
          }

          return new EntityCrudOutputWrapper($pack);
        });
  }
}

Hope it helps.

d1urno commented 5 years ago

Thank you very much for the guide, it helps a lot!

pmelab commented 4 years ago

Custom mutations are the preferred solution. Closing this issue as resolved.