Skip to content

Commit

Permalink
Merge pull request #7202 from craftcms/feature/graphql-relation-argum…
Browse files Browse the repository at this point in the history
…ents

Feature/graphql relation arguments

Resolves #7110
Resolves #6911
  • Loading branch information
brandonkelly committed Dec 2, 2020
2 parents 0b03372 + c1c7b67 commit e0c0d79
Show file tree
Hide file tree
Showing 24 changed files with 1,029 additions and 106 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG-v3.6.md
Expand Up @@ -18,6 +18,7 @@
- Added the `rasterizeSvgThumbs` config setting. ([#7146](https://github.com/craftcms/cms/issues/7146))
- Added the `{% tag %}` Twig tag.
- Added the `withGroups` user query param.
- Added the `relatedToAssets`, `relatedToCategories`, `relatedToEntries`, `relatedToTags`, and `relatedToUsers` arguments to GraphQL queries. ([#7110](https://github.com/craftcms/cms/issues/7110))
- Added `craft\base\ElementExporterInterface::isFormattable()`.
- Added `craft\base\VolumeTrait::$titleTranslationMethod`.
- Added `craft\base\VolumeTrait::$titleTranslationKeyFormat`.
Expand All @@ -27,7 +28,23 @@
- Added `craft\elements\Entry::EVENT_DEFINE_ENTRY_TYPES`. ([#7136](https://github.com/craftcms/cms/issues/7136))
- Added `craft\elements\Entry::getAvailableEntryTypes()`.
- Added `craft\events\DefineEntryTypesEvent`.
- Added `craft\events\RegisterGqlArgumentHandlersEvent`.
- Added `craft\fieldlayoutelements\AssetTitleField`.
- Added `craft\gql\ArgumentManager`.
- Added `craft\gql\base\ArgumentHandler`.
- Added `craft\gql\base\ArgumentHandlerInterface`.
- Added `craft\gql\base\RelationArgumentHandler`.
- Added `craft\gql\ElementQueryConditionBuilder::setArgumentManager()`.
- Added `craft\gql\handlers\RelatedAssets`.
- Added `craft\gql\handlers\RelatedCategories`.
- Added `craft\gql\handlers\RelatedEntries`.
- Added `craft\gql\handlers\RelatedTags`.
- Added `craft\gql\handlers\RelatedUsers`.
- Added `craft\gql\types\input\criteria\Asset`.
- Added `craft\gql\types\input\criteria\Category`.
- Added `craft\gql\types\input\criteria\Entry`.
- Added `craft\gql\types\input\criteria\Tag`.
- Added `craft\gql\types\input\criteria\User`.
- Added `craft\helpers\Gql::eagerLoadComplexity()`.
- Added `craft\helpers\Gql::nPlus1Complexity()`.
- Added `craft\helpers\Gql::singleQueryComplexity()`.
Expand Down Expand Up @@ -70,6 +87,8 @@
- Deprecated the `siteName` config setting. Sites’ Name settings should be set to environment variables instead.
- Deprecated the `siteUrl` config setting. Sites’ Base URL settings should be set to aliases or environment variables instead. ([#3205](https://github.com/craftcms/cms/issues/3205))
- Deprecated `craft\db\Connection::trimObjectName()`.
- Deprecated `craft\gql\base\Resolver::getArrayableArguments()`.
- Deprecated `craft\gql\base\Resolver::prepareArguments()`.
- Deprecated `craft\helpers\App::logConfig()`.
- Deprecated `craft\services\Composer::$disablePackagist`.
- Deprecated `craft\web\View::$minifyCss`.
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,23 @@
- It’s now possible to set sites’ Name settings to environment variables.
- Added the `{% tag %}` Twig tag.
- Added the `withGroups` user query param.
- Added the `relatedToAssets`, `relatedToCategories`, `relatedToEntries`, `relatedToTags`, and `relatedToUsers` arguments to GraphQL queries. ([#7110](https://github.com/craftcms/cms/issues/7110))
- Added `craft\events\RegisterGqlArgumentHandlersEvent`.
- Added `craft\gql\ArgumentManager`.
- Added `craft\gql\base\ArgumentHandler`.
- Added `craft\gql\base\ArgumentHandlerInterface`.
- Added `craft\gql\base\RelationArgumentHandler`.
- Added `craft\gql\ElementQueryConditionBuilder::setArgumentManager()`.
- Added `craft\gql\handlers\RelatedAssets`.
- Added `craft\gql\handlers\RelatedCategories`.
- Added `craft\gql\handlers\RelatedEntries`.
- Added `craft\gql\handlers\RelatedTags`.
- Added `craft\gql\handlers\RelatedUsers`.
- Added `craft\gql\types\input\criteria\Asset`.
- Added `craft\gql\types\input\criteria\Category`.
- Added `craft\gql\types\input\criteria\Entry`.
- Added `craft\gql\types\input\criteria\Tag`.
- Added `craft\gql\types\input\criteria\User`.
- Added `craft\models\Site::getName()`.
- Added `craft\models\Site::setBaseUrl()`.
- Added `craft\models\Site::setName()`.
Expand All @@ -20,6 +37,8 @@
### Deprecated
- Deprecated the `siteName` config setting. Sites’ Name settings should be set to environment variables instead.
- Deprecated the `siteUrl` config setting. Sites’ Base URL settings should be set to aliases or environment variables instead. ([#3205](https://github.com/craftcms/cms/issues/3205))
- Deprecated `craft\gql\base\Resolver::getArrayableArguments()`.
- Deprecated `craft\gql\base\Resolver::prepareArguments()`.

### Fixed
- Fixed a JavaScript error that occurred when opening an element selector modal. ([#7186](https://github.com/craftcms/cms/issues/7186))
Expand Down
24 changes: 24 additions & 0 deletions src/events/RegisterGqlArgumentHandlersEvent.php
@@ -0,0 +1,24 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\events;

use yii\base\Event;

/**
* RegisterGqlArgumentHandlersEvent class.
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 3.6.0
*/
class RegisterGqlArgumentHandlersEvent extends Event
{
/**
* @var array[] List of Argument handler class names.
*/
public $handlers = [];
}
151 changes: 151 additions & 0 deletions src/gql/ArgumentManager.php
@@ -0,0 +1,151 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\gql;

use Craft;
use craft\base\Component;
use craft\events\DefineGqlTypeFieldsEvent;
use craft\events\RegisterGqlArgumentHandlersEvent;
use craft\gql\base\ArgumentHandlerInterface;
use craft\gql\handlers\RelatedAssets;
use craft\gql\handlers\RelatedCategories;
use craft\gql\handlers\RelatedEntries;
use craft\gql\handlers\RelatedTags;
use craft\gql\handlers\RelatedUsers;

/**
* Class ArgumentManager
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 3.6.0
*/
class ArgumentManager extends Component
{
/**
* @event RegisterGqlArgumentHandlers The event that is triggered when GraphQL argument are being handled.
*
* Plugins can use this event to add, remove or modify arguments on a given GraphQL type.
*
* ---
* ```php
* use craft\events\RegisterGqlArgumentHandlers;
* use craft\gql\ArgumentManager;
* use my/namespace/gql/handlers/RedirectHandler
* use yii\base\Event;
*
* Event::on(ArgumentManager::class, ArgumentManager::EVENT_DEFINE_GQL_ARGUMENT_HANDLERS, function(RegisterGqlArgumentHandlersEvent $event) {
* // Register a handler that works on the `redirect` argument.
* $event->handlers['redirect'] = RedirectHandler::class;
* };
* });
* ```
*/
const EVENT_DEFINE_GQL_ARGUMENT_HANDLERS = 'defineGqlArgumentHandlers';

private $_argumentHandlers = [];

private $_handlersCreated = false;

public function init()
{
$handlers = [
'relatedToEntries' => RelatedEntries::class,
'relatedToAssets' => RelatedAssets::class,
'relatedToCategories' => RelatedCategories::class,
'relatedToTags' => RelatedTags::class,
'relatedToUsers' => RelatedUsers::class,
];

$event= new RegisterGqlArgumentHandlersEvent([
'handlers' => $handlers
]);

$this->trigger(self::EVENT_DEFINE_GQL_ARGUMENT_HANDLERS, $event);

$this->_argumentHandlers = $event->handlers;
}

/**
* Create all the handlers, if class names were provided.
*/
protected function createHandlers(): void
{
if ($this->_handlersCreated) {
return;
}

foreach ($this->_argumentHandlers as &$handler) {
// Instantiate in place, if a class name is added.
if (is_string($handler)) {
$handler = $this->createHandler($handler);
}
}

unset($handler);
$this->_handlersCreated = true;
}

/**
* Set the argument handler for an argument name.
*
* @param string $argumentName
* @param string|ArgumentHandlerInterface $handler
*/
public function setHandler(string $argumentName, $handler): void
{
if (is_string($handler)) {
$handler = $this->createHandler($handler);
}

$this->_argumentHandlers[$argumentName] = $handler;
}

/**
* Prepare GraphQL arguments according to the registered argument handlers.
*
* @param $arguments
* @return array
* @throws \yii\base\InvalidConfigException
*/
public function prepareArguments($arguments): array {
$this->createHandlers();

// TODO remove in Craft 4.1
if (isset($arguments['relatedToAll'])) {
Craft::$app->getDeprecator()->log('graphql.arguments.relatedToAll', 'The `relatedToAll` argument has been deprecated. Use the `relatedTo` argument with the `["and", ...ids]` syntax instead.');
$ids = (array)$arguments['relatedToAll'];
$ids = array_map(function($value) {
return ['element' => $value];
}, $ids);
$arguments['relatedTo'] = array_merge(['and'], $ids);
unset($arguments['relatedToAll']);
}

foreach ($this->_argumentHandlers as $argumentName => $handler) {
if (!empty($arguments[$argumentName]) && $handler instanceof ArgumentHandlerInterface) {
$arguments = $handler->handleArgumentCollection($arguments);
}
}

return $arguments;
}

/**
* @param string $handler
* @return ArgumentHandlerInterface|string
*/
protected function createHandler(string $handler)
{
if (is_a($handler, ArgumentHandlerInterface::class, true)) {
/** @var ArgumentHandlerInterface $handler */
$handler = new $handler();
$handler->setArgumentManager($this);
}
return $handler;
}
}
21 changes: 19 additions & 2 deletions src/gql/ElementQueryConditionBuilder.php
Expand Up @@ -74,6 +74,12 @@ class ElementQueryConditionBuilder extends Component
* @var ResolveInfo
*/
private $_resolveInfo;

/**
* @var ArgumentManager
*/
private $_argumentManager;

private $_fragments;
private $_eagerLoadableFieldsByContext = [];
private $_transformableAssetProperties = ['url', 'width', 'height'];
Expand Down Expand Up @@ -115,6 +121,17 @@ public function setResolveInfo(ResolveInfo $resolveInfo)
$this->_fragments = $this->_resolveInfo->fragments;
}

/**
* Set the current ResolveInfo object.
*
* @param ArgumentManager $argumentManager
* @since 3.6.0
*/
public function setArgumentManager(ArgumentManager $argumentManager)
{
$this->_argumentManager = $argumentManager;
}

/**
* Extract the query conditions based on the resolve information passed in the constructor.
* Returns an array of [methodName => parameters] to be called on the element query.
Expand Down Expand Up @@ -191,7 +208,7 @@ private function _extractArgumentValue(Node $argumentNode)
$extractedValue = $argumentNodeValue->value;
}
} else {
$extractedValue = $argumentNodeValue;
$extractedValue = $argumentNode->kind === 'IntValue' ? (int) $argumentNodeValue : $argumentNodeValue;
}

return $extractedValue;
Expand Down Expand Up @@ -453,7 +470,7 @@ private function _traversAndBuildPlans(Node $parentNode, $context = 'global', Fi

// For relational fields, prepare the arguments.
if ($craftContentField instanceof BaseRelationField) {
$arguments = ElementResolver::prepareArguments($arguments);
$arguments = $this->_argumentManager->prepareArguments($arguments);
}
}

Expand Down
50 changes: 50 additions & 0 deletions src/gql/base/ArgumentHandler.php
@@ -0,0 +1,50 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\gql\base;


use craft\gql\ArgumentManager;

/**
* Class ArgumentHandler
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 3.6.0
*/
abstract class ArgumentHandler implements ArgumentHandlerInterface
{
/** @var ArgumentManager */
protected $argumentManager;

/** @var string */
protected $argumentName;

/**
* @inheritdoc
*/
public function setArgumentManager(ArgumentManager $argumentManager): void {
$this->argumentManager = $argumentManager;
}

/**
* Handle a single argument value
* @param $argumentValue
* @return mixed
*/
abstract protected function handleArgument($argumentValue);

/**
* @inheritdoc
*/
public function handleArgumentCollection(array $argumentList = []): array
{
$argumentList[$this->argumentName] = $this->handleArgument($this->argumentName);

return $argumentList;
}
}
35 changes: 35 additions & 0 deletions src/gql/base/ArgumentHandlerInterface.php
@@ -0,0 +1,35 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\gql\base;


use craft\gql\ArgumentManager;

/**
* Class ArgumentHandlerInterface
*
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
* @since 3.6.0
*/
interface ArgumentHandlerInterface
{
/**
* Handle an argument collection
*
* @param array $argumentList argument list to be used for the query
* @return mixed
*/
public function handleArgumentCollection(array $argumentList = []): array;

/**
* Set the current argument manager. Required for recursive argument preparation.
*
* @param ArgumentManager $argumentManager
*/
public function setArgumentManager(ArgumentManager $argumentManager): void;
}

0 comments on commit e0c0d79

Please sign in to comment.