├── .gitignore
├── Actions
├── DissolveMediaFolderAction.php
├── GenerateIntegrationKeyAction.php
├── GenerateSalesChannelKeyAction.php
├── GenerateUserKeyAction.php
├── ProvideFileNameAction.php
└── RenameMediaAction.php
├── Api
├── ApiController.php
├── SalesChannelApiController.php
└── UnsupportedContentTypeException.php
├── CustomFields
└── GraphQLField.php
├── DependencyInjection
├── CompilerPass
│ └── CustomFieldRegistryCompilerPass.php
└── services.xml
├── Resolver
├── AssociationResolver.php
├── CriteriaParser.php
├── QueryResolver.php
├── QueryResolvingException.php
├── SalesChannelQueryResolver.php
└── Struct
│ ├── AggregationBucketStruct.php
│ ├── AggregationKeyStruct.php
│ ├── AggregationResultStruct.php
│ ├── AggregationStruct.php
│ ├── ConnectionStruct.php
│ ├── EdgeStruct.php
│ └── PageInfoStruct.php
├── Resources
├── config
│ └── routes.yaml
└── schema.graphql
├── SalesChannelActions
├── AddToCartAction.php
├── CheckoutAction.php
├── GetCartAction.php
├── LoginAction.php
├── PaymentAction.php
├── RemoveLineItemAction.php
├── UpdateContextAction.php
└── UpdateLineItemAction.php
├── Schema
├── CustomFieldRegistry.php
├── CustomTypes.php
├── Mutation.php
├── SchemaBuilder
│ ├── EnumBuilder.php
│ ├── FieldBuilder.php
│ ├── FieldBuilderCollection.php
│ └── ObjectBuilder.php
├── SchemaFactory.php
└── TypeRegistry.php
├── SwagGraphQL.php
├── Test
├── Actions
│ ├── DissolveMediaFolderActionTest.php
│ ├── GenerateIntegrationKeyActionTest.php
│ ├── GenerateSalesChannelKeyActionTest.php
│ ├── GenerateUserKeyActionTest.php
│ ├── ProvideFileNameActionTest.php
│ └── RenameMediaActionTest.php
├── Api
│ └── ApiControllerTest.php
├── Resolver
│ ├── AssociationResolverTest.php
│ ├── CriteriaParserTest.php
│ └── Struct
│ │ ├── AggregationStructTest.php
│ │ ├── ConnectionStructTest.php
│ │ ├── EdgeStructTest.php
│ │ └── PageInfoStructTest.php
├── Schema
│ ├── CustomTypesTest.php
│ ├── MutationTest.php
│ ├── SchemaFactoryTest.php
│ └── TypeRegistryTest.php
├── TestKernel.php
├── Traits
│ ├── GraphqlApiTest.php
│ └── SchemaTestTrait.php
└── _fixtures
│ ├── AssociationEntity.php
│ ├── BaseEntity.php
│ ├── BaseEntityWithDefaults.php
│ ├── ManyToManyEntity.php
│ ├── ManyToOneEntity.php
│ └── MappingEntity.php
├── TestBootstrap.php
├── Types
├── DateType.php
└── JsonType.php
├── bin
├── phpstan.sh
├── phpunit-coverage.sh
└── phpunit.sh
├── composer.json
├── phpstan.neon
├── phpunit.xml.dist
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS generated files
2 | .DS_Store
3 | .DS_Store?
4 | ._*
5 | .Spotlight-V100
6 | .Trashes
7 | ./Icon?
8 | ehthumbs.db
9 | Thumbs.db
10 | # SASS/SCSS cache
11 | .sass-cache/
12 |
13 | # PhpStorm-Project
14 | /.idea/
15 |
16 | # Composer
17 | /composer.phar
18 | /vendor/*
19 | /composer.lock
20 |
21 | # Caches/Proxies
22 | .php_cs
23 | .php_cs.cache
24 | /var/*
25 | /var/bootstrap.php.cache
26 | /var/config_administration_plugins.json
27 | /var/jwt/public.pem
28 | /var/jwt/private.pem
29 |
30 | /public/*
31 | !/public/index.php
32 | !/public/.htaccess
33 | /public/css/*
34 | !/public/css/
35 | !/public/css/.gitkeep
36 | /public/js/*
37 | !/public/js/
38 | !/public/js/.gitkeep
39 | /public/media/*
40 | !/public/media/
41 | !/public/media/.gitkeep
42 | src/Administration/Resources/public/static/
43 |
44 | /files/documents/*
45 | !/files/documents/.htaccess
46 | /files/downloads/*
47 |
48 | # Snippet exports
49 | /snippetsExport/
50 |
51 | !**/.gitkeep
52 |
53 | # Log files
54 | /var/logs/*
55 | !/var/logs/.htaccess
56 | /var/log/*
57 | !/var/log/.htaccess
58 | npm-debug.log
59 |
60 | # User configurations
61 | app/config/parameters.yml
62 |
63 | # tooling
64 | /dev-ops/docker/_volumes
65 | /docker-compose.override.yml
66 | /.psh.yml
67 | /dev-ops/docker/containers/*/createuser.sh
68 | /dev-ops/docker/containers/php7/Dockerfile
69 | /.psh.yaml.override
70 | /.psh.yml.override
71 | /.env
72 | /dev-ops/check_requirements.php
73 | /performance/_fixtures.php
74 |
75 | # tests / ci
76 | /build/
77 | !build/artifacts/.gitkeep
78 | ###> symfony/phpunit-bridge ###
79 | .phpunit
80 | /phpunit.xml
81 | ###< symfony/phpunit-bridge ###
82 |
83 | ###> phpunit/phpunit ###
84 | /phpunit.xml
85 | ###< phpunit/phpunit ###
86 |
87 | ###> symfony/framework-bundle ###
88 | .env
89 | /public/bundles/
90 | /var/
91 | /vendor/
92 | ###< symfony/framework-bundle ###
93 |
--------------------------------------------------------------------------------
/Actions/DissolveMediaFolderAction.php:
--------------------------------------------------------------------------------
1 | mediaFolderService = $mediaFolderService;
24 | }
25 |
26 | public function returnType(): Type
27 | {
28 | return Type::nonNull(Type::id());
29 | }
30 |
31 | public function defineArgs(): FieldBuilderCollection
32 | {
33 | return FieldBuilderCollection::create()
34 | ->addField(self::FOLDER_ID_ARGUMENT, Type::nonNull(Type::id()));
35 | }
36 |
37 | public function description(): string
38 | {
39 | return 'Dissolves a media folder and puts the content one level higher.';
40 | }
41 |
42 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
43 | {
44 | $folderId = $args[self::FOLDER_ID_ARGUMENT];
45 | $this->mediaFolderService->dissolve($folderId, $context);
46 |
47 | return $folderId;
48 | }
49 | }
--------------------------------------------------------------------------------
/Actions/GenerateIntegrationKeyAction.php:
--------------------------------------------------------------------------------
1 | 'IntegrationAccessKey',
19 | 'fields' => [
20 | 'accessKey' => [
21 | 'type' => Type::nonNull(Type::id())
22 | ],
23 | 'secretAccessKey' => [
24 | 'type' => Type::nonNull(Type::id())
25 | ]
26 | ]
27 | ]);
28 | }
29 |
30 | public function defineArgs(): FieldBuilderCollection
31 | {
32 | return FieldBuilderCollection::create();
33 | }
34 |
35 | public function description(): string
36 | {
37 | return 'Generates access keys for integrations.';
38 | }
39 |
40 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
41 | {
42 | return [
43 | 'accessKey' => AccessKeyHelper::generateAccessKey('integration'),
44 | 'secretAccessKey' => AccessKeyHelper::generateSecretAccessKey(),
45 | ];
46 | }
47 | }
--------------------------------------------------------------------------------
/Actions/GenerateSalesChannelKeyAction.php:
--------------------------------------------------------------------------------
1 | 'KeyPair',
19 | 'fields' => [
20 | 'accessKey' => [
21 | 'type' => Type::nonNull(Type::id())
22 | ],
23 | 'secretAccessKey' => [
24 | 'type' => Type::nonNull(Type::id())
25 | ]
26 | ]
27 | ]);
28 | }
29 |
30 | public function defineArgs(): FieldBuilderCollection
31 | {
32 | return FieldBuilderCollection::create();
33 | }
34 |
35 | public function description(): string
36 | {
37 | return 'Generates the access keys for a user.';
38 | }
39 |
40 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
41 | {
42 | return [
43 | 'accessKey' => AccessKeyHelper::generateAccessKey('user'),
44 | 'secretAccessKey' => AccessKeyHelper::generateSecretAccessKey(),
45 | ];
46 | }
47 | }
--------------------------------------------------------------------------------
/Actions/ProvideFileNameAction.php:
--------------------------------------------------------------------------------
1 | nameProvider = $nameProvider;
26 | }
27 |
28 | public function returnType(): Type
29 | {
30 | return Type::nonNull(Type::string());
31 | }
32 |
33 | public function defineArgs(): FieldBuilderCollection
34 | {
35 | return FieldBuilderCollection::create()
36 | ->addField(self::FILE_NAME_ARGUMENT, Type::nonNull(Type::string()))
37 | ->addField(self::FILE_EXTENSION_ARGUMENT, Type::nonNull(Type::string()))
38 | ->addField(self::MEDIA_ID_ARGUMENT, Type::id());
39 | }
40 |
41 | public function description(): string
42 | {
43 | return 'Provides a unique filename based on the given one.';
44 | }
45 |
46 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
47 | {
48 | $fileName = $args[self::FILE_NAME_ARGUMENT];
49 | $fileExtension = $args[self::FILE_EXTENSION_ARGUMENT];
50 | $mediaId = array_key_exists(self::MEDIA_ID_ARGUMENT, $args) ?
51 | $args[self::FILE_NAME_ARGUMENT] :
52 | null;
53 |
54 | return $this->nameProvider->provide($fileName, $fileExtension, $mediaId, $context);
55 | }
56 | }
--------------------------------------------------------------------------------
/Actions/RenameMediaAction.php:
--------------------------------------------------------------------------------
1 | typeRegistry = $typeRegistry;
44 | $this->fileSaver = $fileSaver;
45 | $this->mediaRepository = $mediaRepository;
46 | }
47 |
48 | public function returnType(): Type
49 | {
50 | return $this->typeRegistry->getObjectForDefinition(MediaDefinition::class);
51 | }
52 |
53 | public function defineArgs(): FieldBuilderCollection
54 | {
55 | return FieldBuilderCollection::create()
56 | ->addField(self::MEDIA_ID_ARGUMENT, Type::nonNull(Type::id()))
57 | ->addField(self::FILENAME_ARGUMENT, Type::nonNull(Type::string()));
58 | }
59 |
60 | public function description(): string
61 | {
62 | return 'Renames the file with the given ID.';
63 | }
64 |
65 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
66 | {
67 | $mediaId = $args[self::MEDIA_ID_ARGUMENT];
68 | $fileName = $args[self::FILENAME_ARGUMENT];
69 |
70 | $this->fileSaver->renameMedia($mediaId, $fileName, $context);
71 |
72 | $criteria = new Criteria();
73 | $criteria->addFilter(new EqualsFilter('id', $mediaId));
74 | AssociationResolver::addAssociations($criteria, $info->lookahead()->queryPlan(), MediaDefinition::class);
75 |
76 | return $this->mediaRepository->search($criteria, $context)->get($mediaId);
77 | }
78 | }
--------------------------------------------------------------------------------
/Api/ApiController.php:
--------------------------------------------------------------------------------
1 | schema = $schema;
28 | $this->queryResolver = $queryResolver;
29 | }
30 |
31 | /**
32 | * @Route("/graphql/generate-schema", name="graphql_generate_schema", methods={"GET"})
33 | *
34 | * @return \Symfony\Component\HttpFoundation\Response
35 | */
36 | public function generateSchema(): Response
37 | {
38 | file_put_contents(__DIR__ . '/../Resources/schema.graphql', SchemaPrinter::doPrint($this->schema));
39 | return new Response();
40 | }
41 |
42 | /**
43 | * GraphQL Endpoint
44 | *
45 | * supports: @see https://graphql.github.io/learn/serving-over-http/#http-methods-headers-and-body
46 | * GET: query as query string
47 | * POST with JSON: query in body like {'query': '...'}
48 | * POST with application/graphql: query is complete body
49 | *
50 | * @Route("/graphql", name="graphql", methods={"GET|POST"})
51 | *
52 | * @param Request $request
53 | * @param Context $context
54 | * @return \Symfony\Component\HttpFoundation\Response
55 | * @throws UnsupportedContentTypeException
56 | */
57 | public function query(Request $request, Context $context): Response
58 | {
59 | $query = null;
60 | $variables = null;
61 | if ($request->getMethod() === Request::METHOD_POST) {
62 | $contentType = explode(';', $request->headers->get('content_type'))[0];
63 | if ($contentType === 'application/json') {
64 | /** @var string $content */
65 | $content = $request->getContent();
66 | $body = json_decode($content, true);
67 | $query = $body['query'];
68 | $variables = $body['variables'] ?? null;
69 | } else if ($contentType === 'application/graphql') {
70 | $query = $request->getContent();
71 | } else {
72 | throw new UnsupportedContentTypeException(
73 | $contentType,
74 | 'application/json',
75 | 'application/graphql'
76 | );
77 | }
78 | } else {
79 | $query = $request->query->get('query');
80 | }
81 |
82 | $result = GraphQL::executeQuery(
83 | $this->schema,
84 | $query,
85 | null,
86 | $context,
87 | $variables,
88 | null,
89 | // Default Resolver
90 | function ($rootValue, $args, $context, ResolveInfo $info) {
91 | return $this->queryResolver->resolve($rootValue, $args, $context, $info);
92 | }
93 | );
94 |
95 | return new JsonResponse($result->toArray());
96 | }
97 | }
--------------------------------------------------------------------------------
/Api/SalesChannelApiController.php:
--------------------------------------------------------------------------------
1 | schema = $schema;
30 | $this->queryResolver = $queryResolver;
31 | }
32 |
33 | /**
34 | * @Route("/storefront-api/v{version}/graphql/generate-schema", name="storefront-api.graphql_generate_schema", methods={"GET"})
35 | *
36 | * @return \Symfony\Component\HttpFoundation\Response
37 | */
38 | public function generateSchema(): Response
39 | {
40 | file_put_contents(__DIR__ . '/../Resources/sales-channel-schema.graphql', SchemaPrinter::doPrint($this->schema));
41 | return new Response();
42 | }
43 |
44 | /**
45 | * GraphQL Endpoint
46 | *
47 | * supports: @see https://graphql.github.io/learn/serving-over-http/#http-methods-headers-and-body
48 | * GET: query as query string
49 | * POST with JSON: query in body like {'query': '...'}
50 | * POST with application/graphql: query is complete body
51 | *
52 | * @Route("/storefront-api/v{version}/graphql", name="storefront-api.graphql", methods={"GET|POST"})
53 | *
54 | * @param Request $request
55 | * @param SalesChannelContext $context
56 | * @return \Symfony\Component\HttpFoundation\Response
57 | * @throws UnsupportedContentTypeException
58 | */
59 | public function query(Request $request, SalesChannelContext $context): Response
60 | {
61 | $query = null;
62 | $variables = null;
63 | if ($request->getMethod() === Request::METHOD_POST) {
64 | if ($request->headers->get('content_type') === 'application/json') {
65 | /** @var string $content */
66 | $content = $request->getContent();
67 | $body = json_decode($content, true);
68 | $query = $body['query'];
69 | $variables = $body['variables'] ?? null;
70 | } else if ($request->headers->get('content_type') === 'application/graphql') {
71 | $query = $request->getContent();
72 | } else {
73 | /** @var string $contentType */
74 | $contentType = $request->headers->get('content_type');
75 | throw new UnsupportedContentTypeException(
76 | $contentType,
77 | 'application/json',
78 | 'application/graphql'
79 | );
80 | }
81 | } else {
82 | $query = $request->query->get('query');
83 | }
84 |
85 | $result = GraphQL::executeQuery(
86 | $this->schema,
87 | $query,
88 | null,
89 | $context,
90 | $variables,
91 | null,
92 | // Default Resolver
93 | function ($rootValue, $args, $context, ResolveInfo $info) {
94 | return $this->queryResolver->resolve($rootValue, $args, $context, $info);
95 | }
96 | );
97 |
98 | return new JsonResponse($result->toArray());
99 | }
100 | }
--------------------------------------------------------------------------------
/Api/UnsupportedContentTypeException.php:
--------------------------------------------------------------------------------
1 | collectQueries($container);
14 | $this->collectMutations($container);
15 | $this->collectSalesChannelQueries($container);
16 | $this->collectSalesChannelMutations($container);
17 | }
18 |
19 | private function collectQueries(ContainerBuilder $container): void
20 | {
21 | $services = $container->findTaggedServiceIds('swag_graphql.queries');
22 | $registry = $container->getDefinition('swag_graphql.query_registry');
23 |
24 | foreach ($services as $serviceId => $attributes) {
25 | $query = null;
26 | foreach ($attributes as $attr) {
27 | if (array_key_exists('query', $attr)) {
28 | $query = $attr['query'];
29 | break;
30 | }
31 |
32 | throw new \RuntimeException(sprintf('Missing query attribute in service tag for class %s.', $serviceId));
33 | }
34 |
35 | $registry->addMethodCall('addField', [$query, new Reference($serviceId)]);
36 | }
37 | }
38 |
39 | private function collectMutations(ContainerBuilder $container): void
40 | {
41 | $services = $container->findTaggedServiceIds('swag_graphql.mutations');
42 | $registry = $container->getDefinition('swag_graphql.mutation_registry');
43 |
44 | foreach ($services as $serviceId => $attributes) {
45 | $mutation = null;
46 | foreach ($attributes as $attr) {
47 | if (array_key_exists('mutation', $attr)) {
48 | $mutation = $attr['mutation'];
49 | break;
50 | }
51 |
52 | throw new \RuntimeException(sprintf('Missing mutation attribute in service tag for class %s.', $serviceId));
53 | }
54 |
55 | $registry->addMethodCall('addField', [$mutation, new Reference($serviceId)]);
56 | }
57 |
58 | }
59 |
60 | private function collectSalesChannelQueries(ContainerBuilder $container): void
61 | {
62 | $services = $container->findTaggedServiceIds('swag_graphql.sales_channel_queries');
63 | $registry = $container->getDefinition('swag_graphql.sales_channel_query_registry');
64 |
65 | foreach ($services as $serviceId => $attributes) {
66 | $query = null;
67 | foreach ($attributes as $attr) {
68 | if (array_key_exists('query', $attr)) {
69 | $query = $attr['query'];
70 | break;
71 | }
72 |
73 | throw new \RuntimeException(sprintf('Missing query attribute in service tag for class %s.', $serviceId));
74 | }
75 |
76 | $registry->addMethodCall('addField', [$query, new Reference($serviceId)]);
77 | }
78 | }
79 |
80 | private function collectSalesChannelMutations(ContainerBuilder $container): void
81 | {
82 | $services = $container->findTaggedServiceIds('swag_graphql.sales_channel_mutations');
83 | $registry = $container->getDefinition('swag_graphql.sales_channel_mutation_registry');
84 |
85 | foreach ($services as $serviceId => $attributes) {
86 | $mutation = null;
87 | foreach ($attributes as $attr) {
88 | if (array_key_exists('mutation', $attr)) {
89 | $mutation = $attr['mutation'];
90 | break;
91 | }
92 |
93 | throw new \RuntimeException(sprintf('Missing mutation attribute in service tag for class %s.', $serviceId));
94 | }
95 |
96 | $registry->addMethodCall('addField', [$mutation, new Reference($serviceId)]);
97 | }
98 |
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/DependencyInjection/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/Resolver/AssociationResolver.php:
--------------------------------------------------------------------------------
1 | $selection) {
31 | if (count($selection['fields']) > 0) {
32 | if (!$definition::getFields()->has($field) && static::isTechnicalField($field)) {
33 | static::addAssociations($criteria, $selection['fields'], $definition);
34 | continue;
35 | }
36 | $association = static::getAssociationDefinition($definition, $field);
37 | $associationCriteria = CriteriaParser::buildCriteria($selection['args'], $association);
38 | static::addAssociations($associationCriteria, $selection['fields'], $association);
39 | $criteria->addAssociation(sprintf('%s.%s', $definition::getEntityName(), $field), $associationCriteria);
40 | }
41 | }
42 | }
43 |
44 | private static function isTechnicalField(string $field): bool
45 | {
46 | return in_array($field, static::TECHNICAL_FIELDS);
47 | }
48 |
49 | private static function getAssociationDefinition(string $definition,string $association): string
50 | {
51 | /** @var FieldCollection $fields */
52 | $fields = $definition::getFields();
53 | foreach ($fields as $field) {
54 | if ($field->getPropertyName() !== $association) {
55 | continue;
56 | }
57 |
58 | switch (true) {
59 | case $field instanceof ManyToManyAssociationField:
60 | return $field->getReferenceDefinition();
61 | case $field instanceof OneToManyAssociationField:
62 | case $field instanceof ManyToOneAssociationField:
63 | case $field instanceof OneToOneAssociationField:
64 | return $field->getReferenceClass();
65 | }
66 | }
67 |
68 | throw new \Exception(sprintf('Association "%s" on Entity "%s" not found', $association, $definition));
69 | }
70 | }
--------------------------------------------------------------------------------
/Resolver/CriteriaParser.php:
--------------------------------------------------------------------------------
1 | setTotalCountMode(Criteria::TOTAL_COUNT_MODE_EXACT);
18 | static::parsePagination($args, $criteria);
19 | static::parseId($args, $criteria);
20 | static::parseSorting($args, $criteria);
21 | static::parseQuery($args, $criteria, $definition);
22 | static::parseAggregations($args, $criteria, $definition);
23 |
24 | return $criteria;
25 | }
26 |
27 | private static function parsePagination(array $args, Criteria $criteria): void
28 | {
29 | if (isset($args['first'])) {
30 | $criteria->setLimit($args['first']);
31 |
32 | if (isset($args['after'])) {
33 | $criteria->setOffset((int)base64_decode($args['after']));
34 | }
35 |
36 | return;
37 | }
38 |
39 | if (isset($args['last']) && isset($args['before'])) {
40 | $criteria->setLimit($args['last']);
41 |
42 | $criteria->setOffset((int)base64_decode($args['before']) - $criteria->getLimit());
43 | }
44 | }
45 |
46 | private static function parseId(array $args, Criteria $criteria): void
47 | {
48 | if (isset($args['id'])) {
49 | $criteria->addFilter(new EqualsFilter('id', $args['id']));
50 |
51 | return;
52 | }
53 | }
54 |
55 | private static function parseSorting(array $args, Criteria $criteria)
56 | {
57 | if (isset($args['sortBy'])) {
58 | $criteria->addSorting(new FieldSorting($args['sortBy'], $args['sortDirection'] ?? FieldSorting::ASCENDING));
59 | }
60 | }
61 |
62 | private static function parseQuery(array $args, Criteria $criteria, string $definition): void
63 | {
64 | if (isset($args['query'])) {
65 | $args['query'] = static::parseRangeParameter($args['query']);
66 | $e = new SearchRequestException();
67 | $criteria->addFilter(QueryStringParser::fromArray($definition, $args['query'], $e));
68 |
69 | $e->tryToThrow();
70 | }
71 | }
72 |
73 | private static function parseRangeParameter(array $query): array
74 | {
75 | if (isset($query['parameters'])) {
76 | $params = [];
77 | foreach ($query['parameters'] as $param) {
78 | $params[$param['operator']] = $param['value'];
79 | }
80 | $query['parameters'] = $params;
81 | }
82 |
83 | if (isset($query['queries'])) {
84 | foreach ($query['queries'] as $key => $nested) {
85 | $query['queries'][$key] = static::parseRangeParameter($nested);
86 | }
87 | }
88 |
89 | return $query;
90 | }
91 |
92 | private static function parseAggregations(array $args, Criteria $criteria, string $definition): void
93 | {
94 | if (isset($args['aggregations'])) {
95 | $e = new SearchRequestException();
96 | AggregationParser::buildAggregations($definition, $args, $criteria, $e);
97 |
98 | $e->tryToThrow();
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/Resolver/QueryResolver.php:
--------------------------------------------------------------------------------
1 | container = $container;
30 | $this->definitionRegistry = $definitionRegistry;
31 | }
32 |
33 | /**
34 | * Default Resolver
35 | * uses the library provided defaultResolver for meta Fields
36 | * and the resolveQuery() and resolveMutation() function for Query and Mutation Fields
37 | */
38 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
39 | {
40 | $path = $info->path[0];
41 | if (is_array($path)) {
42 | $path = $path[0];
43 | }
44 |
45 | try {
46 | if (strpos($path, '__') === 0) {
47 | return Executor::defaultFieldResolver($rootValue, $args, $context, $info);
48 | }
49 | if ($info->operation->operation !== 'mutation') {
50 | return $this->resolveQuery($rootValue, $args, $context, $info);
51 | }
52 |
53 | return $this->resolveMutation($rootValue, $args, $context, $info);
54 | } catch (\Throwable $e) {
55 | // default error-handler will just show "internal server error"
56 | // therefore throw own Exception
57 | throw new QueryResolvingException($e->getMessage(), 0, $e);
58 | }
59 | }
60 |
61 | /**
62 | * Resolver for Query queries
63 | * On the Root-Level it searches for the Entity with th given Args
64 | * On non Root-Level it returns the get-Value of the Field
65 | */
66 | private function resolveQuery($rootValue, $args, $context, ResolveInfo $info)
67 | {
68 | if ($rootValue === null) {
69 | $entityName = Inflector::singularize($info->fieldName);
70 | $definition = $this->definitionRegistry->get(Inflector::tableize($entityName));
71 | $repo = $this->getRepository($definition);
72 |
73 | $criteria = CriteriaParser::buildCriteria($args, $definition);
74 | AssociationResolver::addAssociations($criteria, $info->lookahead()->queryPlan(), $definition);
75 |
76 | $searchResult = $repo->search($criteria, $context);
77 |
78 | if ($entityName !== $info->fieldName) {
79 | return ConnectionStruct::fromResult($searchResult);
80 | } else {
81 | return $searchResult->getEntities()->first();
82 | }
83 | }
84 |
85 | return $this->getSimpleValue($rootValue, $info);
86 | }
87 |
88 | /**
89 | * Resolver for Mutation queries
90 | * On the Root-Level it checks the action and calls the according function
91 | * On non Root-Level it returns the get-Value of the Field
92 | */
93 | private function resolveMutation($rootValue, $args, $context, ResolveInfo $info)
94 | {
95 | if ($rootValue === null) {
96 | $mutation = Mutation::fromName($info->fieldName);
97 |
98 | switch ($mutation->getAction()) {
99 | case Mutation::ACTION_CREATE:
100 | return $this->create($args, $context, $info, $mutation->getEntityName());
101 | case Mutation::ACTION_UPDATE:
102 | return $this->update($args, $context, $info, $mutation->getEntityName());
103 | case Mutation::ACTION_DELETE:
104 | return $this->delete($args, $context, $mutation->getEntityName());
105 | }
106 | }
107 |
108 | return $this->getSimpleValue($rootValue, $info);
109 | }
110 |
111 | /**
112 | * Creates and returns the entity
113 | */
114 | private function create($args, $context, ResolveInfo $info, string $entity): Entity
115 | {
116 | $definition = $this->definitionRegistry->get($entity);
117 | $repo = $this->getRepository($definition);
118 |
119 | $event = $repo->create([$args], $context);
120 | $id = $event->getEventByDefinition($definition)->getIds()[0];
121 |
122 | $criteria = new Criteria([$id]);
123 | AssociationResolver::addAssociations($criteria, $info->lookahead()->queryPlan(), $definition);
124 |
125 | return $repo->search($criteria, $context)->get($id);
126 | }
127 |
128 | /**
129 | * Update and returns the entity
130 | */
131 | private function update($args, $context, ResolveInfo $info, string $entity): Entity
132 | {
133 | $definition = $this->definitionRegistry->get($entity);
134 | $repo = $this->getRepository($definition);
135 |
136 | $event = $repo->update([$args], $context);
137 | $id = $event->getEventByDefinition($definition)->getIds()[0];
138 |
139 | $criteria = new Criteria([$id]);
140 | AssociationResolver::addAssociations($criteria, $info->lookahead()->queryPlan(), $definition);
141 |
142 | return $repo->search($criteria, $context)->get($id);
143 | }
144 |
145 | /**
146 | * Deletes the entity and returns its ID
147 | */
148 | private function delete($args, $context, string $entity): string
149 | {
150 | $definition = $this->definitionRegistry->get($entity);
151 | $repo = $this->getRepository($definition);
152 |
153 | $event = $repo->delete([$args], $context);
154 | $id = $event->getEventByDefinition($definition)->getIds()[0];
155 |
156 | return $id;
157 | }
158 |
159 | private function getRepository(string $definition): EntityRepositoryInterface
160 | {
161 | $repositoryClass = $definition::getEntityName() . '.repository';
162 |
163 | if ($this->container->has($repositoryClass) === false) {
164 | throw new \Exception('Repository not found: ' . $definition::getEntityName());
165 | }
166 |
167 | /** @var EntityRepositoryInterface $repo */
168 | $repo = $this->container->get($definition::getEntityName() . '.repository');
169 |
170 | return $repo;
171 | }
172 |
173 | private function wrapConnectionType(array $elements): ConnectionStruct
174 | {
175 | return (new ConnectionStruct())->assign([
176 | 'edges' => EdgeStruct::fromElements($elements, 0),
177 | 'total' => 0,
178 | 'pageInfo' => new PageInfoStruct()
179 | ]);
180 | }
181 |
182 | private function getSimpleValue($rootValue, ResolveInfo $info)
183 | {
184 | $result = null;
185 |
186 | $getter = 'get' . ucfirst($info->fieldName);
187 | if (method_exists($rootValue, $getter)) {
188 | $result = $rootValue->$getter();
189 | }
190 | if (is_array($rootValue) && array_key_exists($info->fieldName, $rootValue)) {
191 | $result = $rootValue[$info->fieldName];
192 | }
193 |
194 | if ($result instanceof EntityCollection) {
195 | // ToDo handle args in connections
196 | return $this->wrapConnectionType($result->getElements());
197 | }
198 |
199 | return $result;
200 | }
201 |
202 | }
--------------------------------------------------------------------------------
/Resolver/QueryResolvingException.php:
--------------------------------------------------------------------------------
1 | container = $container;
37 | $this->definitionRegistry = $definitionRegistry;
38 | }
39 |
40 | /**
41 | * Default Resolver
42 | * uses the library provided defaultResolver for meta Fields
43 | * and the resolveQuery() and resolveMutation() function for Query and Mutation Fields
44 | */
45 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
46 | {
47 | $path = $info->path[0];
48 | if (is_array($path)) {
49 | $path = $path[0];
50 | }
51 |
52 | try {
53 | if (strpos($path, '__') === 0) {
54 | return Executor::defaultFieldResolver($rootValue, $args, $context, $info);
55 | }
56 | if ($info->operation->operation !== 'mutation') {
57 | return $this->resolveQuery($rootValue, $args, $context, $info);
58 | }
59 |
60 | return $this->resolveMutation($rootValue, $args, $context, $info);
61 | } catch (\Throwable $e) {
62 | // default error-handler will just show "internal server error"
63 | // therefore throw own Exception
64 | throw new QueryResolvingException($e->getMessage(), 0, $e);
65 | }
66 | }
67 |
68 | /**
69 | * Resolver for Query queries
70 | * On the Root-Level it searches for the Entity with th given Args
71 | * On non Root-Level it returns the get-Value of the Field
72 | * @param $rootValue
73 | * @param $args
74 | * @param SalesChannelContext $context
75 | * @param ResolveInfo $info
76 | * @return 0|mixed|ConnectionStruct|null
77 | * @throws \Shopware\Core\Framework\DataAbstractionLayer\Exception\DefinitionNotFoundException
78 | */
79 | private function resolveQuery($rootValue, $args, $context, ResolveInfo $info)
80 | {
81 | if ($rootValue === null) {
82 | $entityName = Inflector::singularize($info->fieldName);
83 | $definition = $this->definitionRegistry->get(Inflector::tableize($entityName));
84 | $repo = $this->getRepository($definition);
85 |
86 | $criteria = CriteriaParser::buildCriteria($args, $definition);
87 |
88 | $associationFields = $definition::getFields()->filterInstance(OneToManyAssociationField::class);
89 | AssociationResolver::addAssociations($criteria, $info->lookahead()->queryPlan(), $definition);
90 |
91 | if ($definition === CustomerDefinition::class) {
92 | if ($context->getCustomer() === null) {
93 | throw new CustomerNotLoggedInException();
94 | }
95 |
96 | $criteria->addFilter(new EqualsFilter('id', $context->getCustomer()->getId()));
97 | } else {
98 |
99 | /** @var OneToManyAssociationField $associationField */
100 | foreach ($associationFields->getElements() as $associationField) {
101 | if ($associationField->getReferenceClass() === SalesChannelDefinition::class) {
102 | $criteria->addFilter(new EqualsFilter($definition::getEntityName() . '.salesChannels.id', $context->getSalesChannel()->getId()));
103 | }
104 | }
105 | }
106 |
107 | $searchResult = $repo->search($criteria, $context->getContext());
108 |
109 | if ($entityName !== $info->fieldName) {
110 | return ConnectionStruct::fromResult($searchResult);
111 | } else {
112 | return $searchResult->getEntities()->first();
113 | }
114 | }
115 |
116 | return $this->getSimpleValue($rootValue, $info);
117 | }
118 |
119 | /**
120 | * Resolver for Mutation queries
121 | * On the Root-Level it checks the action and calls the according function
122 | * On non Root-Level it returns the get-Value of the Field
123 | */
124 | private function resolveMutation($rootValue, $args, $context, ResolveInfo $info)
125 | {
126 | if ($rootValue === null) {
127 | $mutation = Mutation::fromName($info->fieldName);
128 |
129 | switch ($mutation->getAction()) {
130 | case Mutation::ACTION_CREATE:
131 | return $this->create($args, $context, $info, $mutation->getEntityName());
132 | case Mutation::ACTION_UPDATE:
133 | return $this->update($args, $context, $info, $mutation->getEntityName());
134 | case Mutation::ACTION_DELETE:
135 | return $this->delete($args, $context, $mutation->getEntityName());
136 | }
137 | }
138 |
139 | return $this->getSimpleValue($rootValue, $info);
140 | }
141 |
142 | /**
143 | * Creates and returns the entity
144 | */
145 | private function create($args, $context, ResolveInfo $info, string $entity): Entity
146 | {
147 | $definition = $this->definitionRegistry->get($entity);
148 | $billingAddressId = Uuid::randomHex();
149 |
150 | $args = array_merge_recursive($args, [
151 | 'salesChannelId' => $context->getSalesChannel()->getId(),
152 | 'languageId' => $context->getContext()->getLanguageId(),
153 | 'groupId' => $context->getCurrentCustomerGroup()->getId(),
154 | 'defaultPaymentMethodId' => $context->getPaymentMethod()->getId(),
155 | 'billingAddress' => $billingAddressId,
156 | 'shippingMethod'
157 | ]);
158 |
159 | $repo = $this->getRepository($definition);
160 |
161 | $event = $repo->create([$args], $context->getContext());
162 | $id = $event->getEventByDefinition($definition)->getIds()[0];
163 |
164 | $criteria = new Criteria([$id]);
165 | AssociationResolver::addAssociations($criteria, $info->lookahead()->queryPlan(), $definition);
166 |
167 | return $repo->search($criteria, $context->getContext())->get($id);
168 | }
169 |
170 | /**
171 | * Update and returns the entity
172 | */
173 | private function update($args, $context, ResolveInfo $info, string $entity): Entity
174 | {
175 | $definition = $this->definitionRegistry->get($entity);
176 | $repo = $this->getRepository($definition);
177 |
178 | $event = $repo->update([$args], $context->getContext());
179 | $id = $event->getEventByDefinition($definition)->getIds()[0];
180 |
181 | $criteria = new Criteria([$id]);
182 | AssociationResolver::addAssociations($criteria, $info->lookahead()->queryPlan(), $definition);
183 |
184 | return $repo->search($criteria, $context->getContext())->get($id);
185 | }
186 |
187 | /**
188 | * Deletes the entity and returns its ID
189 | */
190 | private function delete($args, $context, string $entity): string
191 | {
192 | $definition = $this->definitionRegistry->get($entity);
193 | $repo = $this->getRepository($definition);
194 |
195 | $event = $repo->delete([$args], $context->getContext());
196 | $id = $event->getEventByDefinition($definition)->getIds()[0];
197 |
198 | return $id;
199 | }
200 |
201 | private function getRepository(string $definition): EntityRepositoryInterface
202 | {
203 | $repositoryClass = $definition::getEntityName() . '.repository';
204 |
205 | if ($this->container->has($repositoryClass) === false) {
206 | throw new \Exception('Repository not found: ' . $definition::getEntityName());
207 | }
208 |
209 | /** @var EntityRepositoryInterface $repo */
210 | $repo = $this->container->get($definition::getEntityName() . '.repository');
211 |
212 | return $repo;
213 | }
214 |
215 | private function wrapConnectionType(array $elements): ConnectionStruct
216 | {
217 | return (new ConnectionStruct())->assign([
218 | 'edges' => EdgeStruct::fromElements($elements, 0),
219 | 'total' => 0,
220 | 'pageInfo' => new PageInfoStruct()
221 | ]);
222 | }
223 |
224 | private function getSimpleValue($rootValue, ResolveInfo $info)
225 | {
226 | $result = null;
227 | $getter = 'get' . ucfirst($info->fieldName);
228 | if (method_exists($rootValue, $getter)) {
229 | $result = $rootValue->$getter();
230 | }
231 | if (is_array($rootValue) && array_key_exists($info->fieldName, $rootValue)) {
232 | $result = $rootValue[$info->fieldName];
233 | }
234 |
235 | if ($result instanceof EntityCollection) {
236 | // ToDo handle args in connections
237 | return $this->wrapConnectionType($result->getElements());
238 | }
239 |
240 | return $result;
241 | }
242 |
243 | }
--------------------------------------------------------------------------------
/Resolver/Struct/AggregationBucketStruct.php:
--------------------------------------------------------------------------------
1 | results;
21 | }
22 |
23 | /**
24 | * @return AggregationKeyStruct[]
25 | */
26 | public function getKeys(): array
27 | {
28 | return $this->keys;
29 | }
30 |
31 | public static function fromAggregationBucket(array $data): AggregationBucketStruct
32 | {
33 | $keys = static::parseKeys($data);
34 |
35 | unset($data['key']);
36 |
37 | $results = static::parseResults($data);
38 |
39 | return (new static())->assign([
40 | 'keys' => $keys,
41 | 'results' => $results
42 | ]);
43 | }
44 |
45 | private static function parseKeys(array $data): array
46 | {
47 | if ($data['key'] === null) {
48 | return [];
49 | }
50 |
51 | $keys = [];
52 | foreach ($data['key'] as $key => $value) {
53 | $keys[] = (new AggregationKeyStruct())->assign([
54 | 'field' => $key,
55 | 'value' => $value
56 | ]);
57 | }
58 |
59 | return $keys;
60 | }
61 |
62 | private static function parseResults(array $data): array
63 | {
64 | $results = [];
65 | foreach ($data as $type => $value) {
66 | if (\is_array($value)) {
67 | foreach ($value as $key => $count) {
68 | $results[] = (new AggregationResultStruct())->assign([
69 | 'type' => \strval($key),
70 | 'result' => $count
71 | ]);
72 | }
73 | continue;
74 | }
75 | $results[] = (new AggregationResultStruct())->assign([
76 | 'type' => \strval($type),
77 | 'result' => $value
78 | ]);
79 | }
80 | return $results;
81 | }
82 | }
--------------------------------------------------------------------------------
/Resolver/Struct/AggregationKeyStruct.php:
--------------------------------------------------------------------------------
1 | field;
18 | }
19 |
20 | public function getValue(): string
21 | {
22 | return $this->value;
23 | }
24 | }
--------------------------------------------------------------------------------
/Resolver/Struct/AggregationResultStruct.php:
--------------------------------------------------------------------------------
1 | type;
17 | }
18 |
19 | public function getResult()
20 | {
21 | return $this->result;
22 | }
23 | }
--------------------------------------------------------------------------------
/Resolver/Struct/AggregationStruct.php:
--------------------------------------------------------------------------------
1 | name;
20 | }
21 |
22 | /**
23 | * @return AggregationBucketStruct[]
24 | */
25 | public function getBuckets(): array
26 | {
27 | return $this->buckets;
28 | }
29 |
30 | /**
31 | * @return AggregationStruct[]
32 | */
33 | public static function fromCollection(AggregationResultCollection $collection): array
34 | {
35 | $aggregations = [];
36 | foreach ($collection->getElements() as $result) {
37 | $aggregations[] = static::fromAggregationResult($result);
38 | }
39 |
40 | return $aggregations;
41 | }
42 |
43 | public static function fromAggregationResult(AggregationResult $aggregation): AggregationStruct
44 | {
45 | $buckets = [];
46 | foreach ($aggregation->getResult() as $result) {
47 | $buckets[] = AggregationBucketStruct::fromAggregationBucket($result);
48 | }
49 |
50 | return (new AggregationStruct())->assign([
51 | 'name' => $aggregation->getName(),
52 | 'buckets' => $buckets
53 | ]);
54 | }
55 | }
--------------------------------------------------------------------------------
/Resolver/Struct/ConnectionStruct.php:
--------------------------------------------------------------------------------
1 | total;
26 | }
27 |
28 | public function getPageInfo(): PageInfoStruct
29 | {
30 | return $this->pageInfo;
31 | }
32 |
33 | /**
34 | * @return EdgeStruct[]
35 | */
36 | public function getEdges(): array
37 | {
38 | return $this->edges;
39 | }
40 |
41 | /**
42 | * @return AggregationStruct[]
43 | */
44 | public function getAggregations(): array
45 | {
46 | return $this->aggregations;
47 | }
48 |
49 | public static function fromResult(EntitySearchResult $searchResult): ConnectionStruct
50 | {
51 | return (new ConnectionStruct())->assign([
52 | 'total' => $searchResult->getTotal(),
53 | 'pageInfo' => PageInfoStruct::fromCriteria($searchResult->getCriteria(), $searchResult->getTotal()),
54 | 'edges' => EdgeStruct::fromElements($searchResult->getElements(), $searchResult->getCriteria()->getOffset() ?? 0),
55 | 'aggregations' => AggregationStruct::fromCollection($searchResult->getAggregations())
56 | ]);
57 | }
58 | }
--------------------------------------------------------------------------------
/Resolver/Struct/EdgeStruct.php:
--------------------------------------------------------------------------------
1 | node;
19 | }
20 |
21 | public function getCursor(): string
22 | {
23 | return $this->cursor;
24 | }
25 |
26 | public static function fromElements(array $elements, int $offset): array
27 | {
28 | $edges = [];
29 | $index = 1;
30 | foreach ($elements as $element) {
31 | $edges[] = (new EdgeStruct())->assign([
32 | 'node' => $element,
33 | 'cursor' => base64_encode(strval($offset + $index))
34 | ]);
35 |
36 | $index++;
37 | }
38 |
39 | return $edges;
40 | }
41 | }
--------------------------------------------------------------------------------
/Resolver/Struct/PageInfoStruct.php:
--------------------------------------------------------------------------------
1 | endCursor;
25 | }
26 |
27 | public function getHasNextPage(): bool
28 | {
29 | return $this->hasNextPage;
30 | }
31 |
32 | public function getStartCursor(): ?string
33 | {
34 | return $this->startCursor;
35 | }
36 |
37 | public function getHasPreviousPage(): bool
38 | {
39 | return $this->hasPreviousPage;
40 | }
41 |
42 | public static function fromCriteria(Criteria $criteria, int $total): PageInfoStruct
43 | {
44 | $limit = $criteria->getLimit() ?? $total;
45 | $offset = $criteria->getOffset() ?? 0;
46 |
47 | return (new PageInfoStruct())->assign([
48 | 'endCursor' => $total === 0 ? null : base64_encode(strval($limit + $offset)),
49 | 'hasNextPage' => $total >= $limit + $offset,
50 | 'startCursor' => $total === 0 ? null :base64_encode(strval($offset + 1)),
51 | 'hasPreviousPage' => $offset > 0
52 | ]);
53 | }
54 | }
--------------------------------------------------------------------------------
/Resources/config/routes.yaml:
--------------------------------------------------------------------------------
1 | graphql:
2 | resource: ../custom/plugins/SwagGraphQL/Api
3 | type: annotation
--------------------------------------------------------------------------------
/SalesChannelActions/AddToCartAction.php:
--------------------------------------------------------------------------------
1 | cartService = $cartService;
41 | $this->typeRegistry = $typeRegistry;
42 | $this->customTypes = $customTypes;
43 | }
44 |
45 | public function returnType(): Type
46 | {
47 | return $this->customTypes->cart($this->typeRegistry);
48 | }
49 |
50 | public function defineArgs(): FieldBuilderCollection
51 | {
52 | return FieldBuilderCollection::create()
53 | ->addField(self::PRODUCT_ID_ARGUMENT, Type::nonNull(Type::id()))
54 | ->addField(self::QUANTITY_ARGUMENT, Type::nonNull(Type::int()))
55 | ->addField(self::PAYLOAD_ARGUMENT, $this->customTypes->json());
56 | }
57 |
58 | public function description(): string
59 | {
60 | return 'Add a product to the Cart.';
61 | }
62 |
63 | /**
64 | * @param SalesChannelContext $context
65 | */
66 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
67 | {
68 | if (!$context->getCustomer()) {
69 | throw new CustomerNotLoggedInException();
70 | }
71 |
72 | $cart = $this->cartService->getCart($context->getToken(), $context);
73 | $id = $args[self::PRODUCT_ID_ARGUMENT];
74 | $payload = array_replace_recursive(['id' => $id], $args[self::PAYLOAD_ARGUMENT] ?? []);
75 |
76 | $lineItem = (new LineItem($id, ProductCollector::LINE_ITEM_TYPE, $args[self::QUANTITY_ARGUMENT]))
77 | ->setPayload($payload)
78 | ->setRemovable(true)
79 | ->setStackable(true);
80 |
81 | $cart = $this->cartService->add($cart, $lineItem, $context);
82 |
83 | return $cart;
84 | }
85 | }
--------------------------------------------------------------------------------
/SalesChannelActions/CheckoutAction.php:
--------------------------------------------------------------------------------
1 | cartService = $cartService;
41 | $this->typeRegistry = $typeRegistry;
42 | $this->orderRepository = $orderRepository;
43 | }
44 |
45 | public function returnType(): Type
46 | {
47 | return $this->typeRegistry->getObjectForDefinition(OrderDefinition::class);
48 | }
49 |
50 | public function defineArgs(): FieldBuilderCollection
51 | {
52 | return FieldBuilderCollection::create();
53 | }
54 |
55 | public function description(): string
56 | {
57 | return 'Finish the order.';
58 | }
59 |
60 | /**
61 | * @param SalesChannelContext $context
62 | */
63 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
64 | {
65 | if (!$context->getCustomer()) {
66 | throw new CustomerNotLoggedInException();
67 | }
68 |
69 | $cart = $this->cartService->getCart($context->getToken(), $context);
70 | $orderId = $this->cartService->order($cart, $context);
71 | $criteria = new Criteria([$orderId]);
72 | $criteria->addAssociation('addresses');
73 |
74 | $order = $this->orderRepository->search($criteria, $context->getContext())->get($orderId);
75 |
76 | if ($order === null) {
77 | throw new OrderNotFoundException($orderId);
78 | }
79 |
80 | return $order;
81 | }
82 | }
--------------------------------------------------------------------------------
/SalesChannelActions/GetCartAction.php:
--------------------------------------------------------------------------------
1 | cartService = $cartService;
37 | $this->typeRegistry = $typeRegistry;
38 | $this->customTypes = $customTypes;
39 | }
40 |
41 | public function returnType(): Type
42 | {
43 | return $this->customTypes->cart($this->typeRegistry);
44 | }
45 |
46 | public function defineArgs(): FieldBuilderCollection
47 | {
48 | return FieldBuilderCollection::create()
49 | ->addField(self::CART_NAME_ARGUMENT, Type::nonNull(Type::string()));
50 | }
51 |
52 | public function description(): string
53 | {
54 | return 'Get or Create an empty cart.';
55 | }
56 |
57 | /**
58 | * @param SalesChannelContext $context
59 | */
60 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
61 | {
62 | if (!$context->getCustomer()) {
63 | throw new CustomerNotLoggedInException();
64 | }
65 |
66 | return $this->cartService->getCart($context->getToken(), $context, $args[self::CART_NAME_ARGUMENT]);
67 | }
68 | }
--------------------------------------------------------------------------------
/SalesChannelActions/LoginAction.php:
--------------------------------------------------------------------------------
1 | accountService = $accountService;
25 | }
26 |
27 | public function returnType(): Type
28 | {
29 | return Type::nonNull(Type::id());
30 | }
31 |
32 | public function defineArgs(): FieldBuilderCollection
33 | {
34 | return FieldBuilderCollection::create()
35 | ->addField(self::EMAIL_ARGUMENT, Type::nonNull(Type::string()))
36 | ->addField(self::PASSWORD_ARGUMENT, Type::nonNull(Type::string()));
37 | }
38 |
39 | public function description(): string
40 | {
41 | return 'Login with a email and password.';
42 | }
43 |
44 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
45 | {
46 | $email = $args[self::EMAIL_ARGUMENT];
47 | $password = $args[self::PASSWORD_ARGUMENT];
48 | $data = new DataBag(['username' => $email, 'password' => $password]);
49 |
50 | return $this->accountService->loginWithPassword($data, $context);
51 | }
52 | }
--------------------------------------------------------------------------------
/SalesChannelActions/PaymentAction.php:
--------------------------------------------------------------------------------
1 | paymentService = $paymentService;
37 | }
38 |
39 | public function returnType(): Type
40 | {
41 | return Type::string();
42 | }
43 |
44 | public function defineArgs(): FieldBuilderCollection
45 | {
46 | return FieldBuilderCollection::create()
47 | ->addFieldBuilder(FieldBuilder::create(self::ORDER_ID_ARGUMENT, Type::nonNull(Type::id())))
48 | ->addFieldBuilder(FieldBuilder::create(self::FINISH_URL_ARGUMENT, Type::string()));
49 | }
50 |
51 | public function description(): string
52 | {
53 | return 'Pay the order.';
54 | }
55 |
56 | /**
57 | * @param SalesChannelContext $context
58 | */
59 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
60 | {
61 | if (!$context->getCustomer()) {
62 | throw new CustomerNotLoggedInException();
63 | }
64 |
65 | $response = $this->paymentService->handlePaymentByOrder($args[self::ORDER_ID_ARGUMENT], $context, $args[self::FINISH_URL_ARGUMENT] ?? null);
66 |
67 | if ($response) {
68 | return $response->getTargetUrl();
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/SalesChannelActions/RemoveLineItemAction.php:
--------------------------------------------------------------------------------
1 | cartService = $cartService;
40 | $this->typeRegistry = $typeRegistry;
41 | $this->customTypes = $customTypes;
42 | }
43 |
44 | public function returnType(): Type
45 | {
46 | return $this->customTypes->cart($this->typeRegistry);
47 | }
48 |
49 | public function defineArgs(): FieldBuilderCollection
50 | {
51 | return FieldBuilderCollection::create()
52 | ->addField(self::KEY_ARGUMENT, Type::nonNull(Type::id()));
53 | }
54 |
55 | public function description(): string
56 | {
57 | return 'Remove a LineItem from the Cart.';
58 | }
59 |
60 | /**
61 | * @param SalesChannelContext $context
62 | */
63 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
64 | {
65 | if (!$context->getCustomer()) {
66 | throw new CustomerNotLoggedInException();
67 | }
68 |
69 | $cart = $this->cartService->getCart($context->getToken(), $context);
70 | $id = $args[self::KEY_ARGUMENT];
71 |
72 | if (!$cart->has($id)) {
73 | throw new LineItemNotFoundException($id);
74 | }
75 |
76 | $cart = $this->cartService->remove($cart, $id, $context);
77 |
78 | return $cart;
79 | }
80 | }
--------------------------------------------------------------------------------
/SalesChannelActions/UpdateContextAction.php:
--------------------------------------------------------------------------------
1 | contextPersister = $contextPersister;
63 | $this->paymentMethodRepository = $paymentMethodRepository;
64 | $this->shippingMethodRepository = $shippingMethodRepository;
65 | $this->addressRepository = $addressRepository;
66 | }
67 |
68 | public function returnType(): Type
69 | {
70 | return Type::nonNull(Type::id());
71 | }
72 |
73 | public function defineArgs(): FieldBuilderCollection
74 | {
75 | return FieldBuilderCollection::create()
76 | ->addField(self::SHIPPING_METHOD_ARGUMENT, Type::id())
77 | ->addField(self::PAYMENT_METHOD_ARGUMENT, Type::id())
78 | ->addField(self::SHIPPING_ADDRESS_ARGUMENT, Type::id())
79 | ->addField(self::BILLING_ADDRESS_ARGUMENT, Type::id());
80 | }
81 |
82 | public function description(): string
83 | {
84 | return 'Update the context of the currently logged in Customer.';
85 | }
86 |
87 | /**
88 | * @param SalesChannelContext $context
89 | */
90 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
91 | {
92 | $update = [];
93 | if (array_key_exists(self::SHIPPING_METHOD_ARGUMENT, $args)) {
94 | $update[self::SHIPPING_METHOD_ARGUMENT] = $this->validateShippingMethodId($args[self::SHIPPING_METHOD_ARGUMENT], $context);
95 | }
96 | if (array_key_exists(self::PAYMENT_METHOD_ARGUMENT, $args)) {
97 | $update[self::PAYMENT_METHOD_ARGUMENT] = $this->validatePaymentMethodId($args[self::PAYMENT_METHOD_ARGUMENT], $context);
98 | }
99 | if (array_key_exists(self::BILLING_ADDRESS_ARGUMENT, $args)) {
100 | $update[self::BILLING_ADDRESS_ARGUMENT] = $this->validateAddressId($args[self::BILLING_ADDRESS_ARGUMENT], $context);
101 | }
102 | if (array_key_exists(self::SHIPPING_ADDRESS_ARGUMENT, $args)) {
103 | $update[self::SHIPPING_ADDRESS_ARGUMENT] = $this->validateAddressId($args{self::SHIPPING_ADDRESS_ARGUMENT}, $context);
104 | }
105 |
106 | $this->contextPersister->save($context->getToken(), $update);
107 |
108 | return $context->getToken();
109 | }
110 |
111 | private function validatePaymentMethodId(string $paymentMethodId, SalesChannelContext $context): string
112 | {
113 | $criteria = new Criteria();
114 | $criteria->addFilter(new EqualsFilter('payment_method.id', $paymentMethodId));
115 |
116 | $valid = $this->paymentMethodRepository->searchIds($criteria, $context->getContext());
117 | if (!\in_array($paymentMethodId, $valid->getIds(), true)) {
118 | throw new UnknownPaymentMethodException($paymentMethodId);
119 | }
120 |
121 | return $paymentMethodId;
122 | }
123 |
124 | private function validateShippingMethodId(string $shippingMethodId, SalesChannelContext $context): string
125 | {
126 | $criteria = new Criteria();
127 | $criteria->addFilter(new EqualsFilter('shipping_method.id', $shippingMethodId));
128 |
129 | $valid = $this->shippingMethodRepository->searchIds($criteria, $context->getContext());
130 | if (!\in_array($shippingMethodId, $valid->getIds(), true)) {
131 | throw new ShippingMethodNotFoundException($shippingMethodId);
132 | }
133 |
134 | return $shippingMethodId;
135 | }
136 |
137 | private function validateAddressId(string $addressId, SalesChannelContext $context): string
138 | {
139 | if (!$context->getCustomer()) {
140 | throw new CustomerNotLoggedInException();
141 | }
142 |
143 | $addresses = $this->addressRepository->search(new Criteria([$addressId]), $context->getContext());
144 | /** @var CustomerAddressEntity|null $address */
145 | $address = $addresses->get($addressId);
146 |
147 | if (!$address) {
148 | throw new AddressNotFoundException($addressId);
149 | }
150 |
151 | if ($address->getCustomerId() !== $context->getCustomer()->getId()) {
152 | throw new AddressNotFoundException($address->getCustomerId() . '/' . $context->getCustomer()->getId());
153 | }
154 |
155 | return $addressId;
156 | }
157 | }
--------------------------------------------------------------------------------
/SalesChannelActions/UpdateLineItemAction.php:
--------------------------------------------------------------------------------
1 | cartService = $cartService;
56 | $this->typeRegistry = $typeRegistry;
57 | $this->customTypes = $customTypes;
58 | $this->mediaRepository = $mediaRepository;
59 | }
60 |
61 | public function returnType(): Type
62 | {
63 | return $this->customTypes->cart($this->typeRegistry);
64 | }
65 |
66 | public function defineArgs(): FieldBuilderCollection
67 | {
68 | return FieldBuilderCollection::create()
69 | ->addField(self::KEY_ARGUMENT, Type::nonNull(Type::id()))
70 | ->addField(self::QUANTIY_ARGUMENT, Type::int())
71 | ->addField(self::PAYLOAD_ARGUMENT, $this->customTypes->json())
72 | ->addField(self::STACKABLE_ARGUMENT, Type::boolean())
73 | ->addField(self::REMOVABLE_ARGUMENT, Type::boolean())
74 | ->addField(self::PRIORITY_ARGUMENT, Type::int())
75 | ->addField(self::LABEL_ARGUMENT, Type::string())
76 | ->addField(self::DESCRIPTION_ARGUMENT, Type::string())
77 | ->addField(self::COVER_ARGUMENT, Type::id());
78 | }
79 |
80 | public function description(): string
81 | {
82 | return 'Update a LineItem from the Cart.';
83 | }
84 |
85 | /**
86 | * @param SalesChannelContext $context
87 | */
88 | public function resolve($rootValue, $args, $context, ResolveInfo $info)
89 | {
90 | if (!$context->getCustomer()) {
91 | throw new CustomerNotLoggedInException();
92 | }
93 |
94 | $cart = $this->cartService->getCart($context->getToken(), $context);
95 | $id = $args[self::KEY_ARGUMENT];
96 |
97 | if (!$cart->has($id)) {
98 | throw new LineItemNotFoundException($id);
99 | }
100 |
101 | $lineItem = $this->cartService->getCart($context->getToken(), $context)->getLineItems()->get($id);
102 | $this->updateLineItem($lineItem, $args, $context->getContext());
103 |
104 | $cart = $this->cartService->recalculate($cart, $context);
105 |
106 | return $cart;
107 | }
108 |
109 | private function updateLineItem(LineItem $lineItem, array $args, Context $context)
110 | {
111 | if (isset($args[self::QUANTIY_ARGUMENT])) {
112 | $lineItem->setQuantity($args[self::QUANTIY_ARGUMENT]);
113 | }
114 |
115 | if (isset($args[self::STACKABLE_ARGUMENT])) {
116 | $lineItem->setStackable($args[self::STACKABLE_ARGUMENT]);
117 | }
118 |
119 | if (isset($args[self::REMOVABLE_ARGUMENT])) {
120 | $lineItem->setRemovable($args[self::REMOVABLE_ARGUMENT]);
121 | }
122 |
123 | if (isset($args[self::PRIORITY_ARGUMENT])) {
124 | $lineItem->setPriority($args[self::PRIORITY_ARGUMENT]);
125 | }
126 |
127 | if (isset($args[self::LABEL_ARGUMENT])) {
128 | $lineItem->setLabel($args[self::LABEL_ARGUMENT]);
129 | }
130 |
131 | if (isset($args[self::DESCRIPTION_ARGUMENT])) {
132 | $lineItem->setDescription($args[self::DESCRIPTION_ARGUMENT]);
133 | }
134 |
135 | if (isset($args[self::COVER_ARGUMENT])) {
136 | $cover = $this->mediaRepository->search(new Criteria([$args[self::COVER_ARGUMENT]]), $context)->get($args[self::COVER_ARGUMENT]);
137 |
138 | if (!$cover) {
139 | throw new LineItemCoverNotFoundException($args[self::COVER_ARGUMENT], $lineItem->getKey());
140 | }
141 |
142 | $lineItem->setCover($cover);
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/Schema/CustomFieldRegistry.php:
--------------------------------------------------------------------------------
1 | fields[$name] = $field;
14 | }
15 |
16 | public function get(string $name): ?GraphQLField
17 | {
18 | if (!array_key_exists($name, $this->fields)) {
19 | return null;
20 | }
21 |
22 | return $this->fields[$name];
23 | }
24 |
25 | public function getFields(): array
26 | {
27 | return $this->fields;
28 | }
29 | }
--------------------------------------------------------------------------------
/Schema/Mutation.php:
--------------------------------------------------------------------------------
1 | action = $action;
39 | $this->entityName = $entityName;
40 | }
41 |
42 | public function getAction(): string
43 | {
44 | return $this->action;
45 | }
46 |
47 | public function getEntityName(): string
48 | {
49 | return $this->entityName;
50 | }
51 |
52 | public function getName(): string
53 | {
54 | return $this->action . Inflector::classify($this->entityName);
55 | }
56 | }
--------------------------------------------------------------------------------
/Schema/SchemaBuilder/EnumBuilder.php:
--------------------------------------------------------------------------------
1 | config['name'] = $name;
17 | }
18 |
19 | public static function create(string $name): self
20 | {
21 | return new self($name);
22 | }
23 |
24 | public function setDescription(string $description): self
25 | {
26 | $this->config['description'] = $description;
27 |
28 | return $this;
29 | }
30 |
31 | public function addValue(string $value, ?string $name = null, string $description = ''): self
32 | {
33 | $this->config['values'][$name ?? $value] = [
34 | 'value' => $value,
35 | 'description' => $description
36 | ];
37 |
38 | return $this;
39 | }
40 |
41 | public function build(): EnumType
42 | {
43 | return new EnumType($this->config);
44 | }
45 | }
--------------------------------------------------------------------------------
/Schema/SchemaBuilder/FieldBuilder.php:
--------------------------------------------------------------------------------
1 | config['name'] = $name;
17 | $this->config['type'] = $type;
18 | }
19 |
20 | public static function create(string $name, Type $type): self
21 | {
22 | return new self($name, $type);
23 | }
24 |
25 | public function setDescription(string $description): self
26 | {
27 | $this->config['description'] = $description;
28 |
29 | return $this;
30 | }
31 |
32 | public function setArguments(FieldBuilderCollection $arguments) : self
33 | {
34 | $this->config['args'] = $arguments->build();
35 |
36 | return $this;
37 | }
38 |
39 | public function setResolver(callable $callback) : self
40 | {
41 | $this->config['resolve'] = $callback;
42 |
43 | return $this;
44 | }
45 |
46 | public function setDeprecationReason(string $reason) : self
47 | {
48 | $this->config['deprecationReason'] = $reason;
49 |
50 | return $this;
51 | }
52 |
53 | public function setDefault($default) : self
54 | {
55 | $this->config['defaultValue'] = $default;
56 |
57 | return $this;
58 | }
59 |
60 | public function build(): array
61 | {
62 | return $this->config;
63 | }
64 | }
--------------------------------------------------------------------------------
/Schema/SchemaBuilder/FieldBuilderCollection.php:
--------------------------------------------------------------------------------
1 | build();
27 | $this->config[$fieldConfig['name']] = $fieldConfig;
28 |
29 | return $this;
30 | }
31 |
32 | public function addField(string $name, Type $type, ?string $description = null) : self
33 | {
34 | $this->config[$name] = ['type' => $type];
35 | if ($description !== null) {
36 | $this->config[$name]['description'] = $description;
37 | }
38 | return $this;
39 | }
40 |
41 | public function build(): array
42 | {
43 | return $this->config;
44 | }
45 | }
--------------------------------------------------------------------------------
/Schema/SchemaBuilder/ObjectBuilder.php:
--------------------------------------------------------------------------------
1 | config['name'] = $name;
29 | $this->config['fields'] = function () {
30 | foreach ($this->callables as $callable) {
31 | /** @var FieldBuilderCollection $fields */
32 | $fields = $callable();
33 | $this->fields = array_merge($this->fields, $fields->build());
34 | }
35 |
36 | return $this->fields;
37 | };
38 | $this->addField(FieldBuilder::create('__typename', Type::string())->setResolver(function () use ($name) {
39 | return $name;
40 | }));
41 | }
42 |
43 | public static function create(string $name): self
44 | {
45 | return new self($name);
46 | }
47 |
48 | public function addField(FieldBuilder ...$fields): self
49 | {
50 | foreach ($fields as $field) {
51 | $fieldConfig = $field->build();
52 | $this->fields[$fieldConfig['name']] = $fieldConfig;
53 | }
54 |
55 | return $this;
56 | }
57 |
58 | public function addLazyFieldCollection(callable $fields): self
59 | {
60 | $this->callables[] = $fields;
61 | return $this;
62 | }
63 |
64 | public function setDescription(string $description): self
65 | {
66 | $this->config['description'] = $description;
67 |
68 | return $this;
69 | }
70 |
71 | public function setDeprecationReason(string $reason) : self
72 | {
73 | $this->config['deprecationReason'] = $reason;
74 | return $this;
75 | }
76 |
77 | public function build(): ObjectType
78 | {
79 | return new ObjectType($this->config);
80 | }
81 |
82 | public function buildAsInput(): InputObjectType
83 | {
84 | return new InputObjectType($this->config);
85 | }
86 | }
--------------------------------------------------------------------------------
/Schema/SchemaFactory.php:
--------------------------------------------------------------------------------
1 | $typeRegistry->getQuery(),
13 | 'mutation' => $typeRegistry->getMutation(),
14 | ]);
15 | }
16 |
17 | public static function createSalesChannelSchema(TypeRegistry $typeRegistry): Schema
18 | {
19 | return new Schema([
20 | 'query' => $typeRegistry->getSalesChannelQuery(),
21 | 'mutation' => $typeRegistry->getSalesChannelMutation(),
22 | ]);
23 | }
24 | }
--------------------------------------------------------------------------------
/SwagGraphQL.php:
--------------------------------------------------------------------------------
1 | load('services.xml');
17 |
18 | parent::build($container);
19 |
20 | $container->addCompilerPass(new CustomFieldRegistryCompilerPass());
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/Test/Actions/DissolveMediaFolderActionTest.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(DefinitionRegistry::class);
33 | $schema = SchemaFactory::createSchema($this->getContainer()->get(TypeRegistry::class));
34 |
35 | $this->apiController = new ApiController($schema, new QueryResolver($this->getContainer(), $registry));
36 | $this->context = Context::createDefaultContext();
37 |
38 | $this->repository = $this->getContainer()->get('media_folder.repository');
39 | }
40 |
41 | public function testDissolveMediaFolder()
42 | {
43 | $folderId = Uuid::randomHex();
44 |
45 | $data = [
46 | [
47 | 'id' => $folderId,
48 | 'name' => 'test folder',
49 | 'configuration' => [],
50 | ],
51 | ];
52 | $this->repository->create($data, $this->context);
53 | $query = "
54 | mutation {
55 | dissolveMediaFolder(
56 | mediaFolderId: \"$folderId\"
57 | )
58 | }
59 | ";
60 |
61 | $request = $this->createGraphqlRequestRequest($query);
62 | $response = $this->apiController->query($request, $this->context);
63 | static::assertEquals(200, $response->getStatusCode());
64 | $data = json_decode($response->getContent(), true);
65 | static::assertArrayNotHasKey('errors', $data, print_r($data, true));
66 | static::assertEquals(
67 | $data['data']['dissolveMediaFolder'],
68 | $folderId,
69 | print_r($data['data'], true)
70 | );
71 |
72 | $folders = $this->repository->search(new Criteria([$folderId]), $this->context);
73 | static::assertNull($folders->get($folderId));
74 | }
75 | }
--------------------------------------------------------------------------------
/Test/Actions/GenerateIntegrationKeyActionTest.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(DefinitionRegistry::class);
29 | $schema = SchemaFactory::createSchema($this->getContainer()->get(TypeRegistry::class));
30 |
31 | $this->apiController = new ApiController($schema, new QueryResolver($this->getContainer(), $registry));
32 | $this->context = Context::createDefaultContext();
33 | }
34 |
35 | public function testGenerateIntegrationKey()
36 | {
37 | $query = "
38 | query {
39 | generateIntegrationKey {
40 | accessKey
41 | secretAccessKey
42 | }
43 | }
44 | ";
45 |
46 | $request = $this->createGraphqlRequestRequest($query);
47 | $response = $this->apiController->query($request, $this->context);
48 | static::assertEquals(200, $response->getStatusCode());
49 | $data = json_decode($response->getContent(), true);
50 | static::assertArrayNotHasKey('errors', $data, print_r($data, true));
51 | static::assertArrayHasKey(
52 | 'accessKey',
53 | $data['data']['generateIntegrationKey'],
54 | print_r($data['data'], true)
55 | );
56 | static::assertArrayHasKey(
57 | 'secretAccessKey',
58 | $data['data']['generateIntegrationKey'],
59 | print_r($data['data'], true)
60 | );
61 | }
62 | }
--------------------------------------------------------------------------------
/Test/Actions/GenerateSalesChannelKeyActionTest.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(DefinitionRegistry::class);
29 | $schema = SchemaFactory::createSchema($this->getContainer()->get(TypeRegistry::class));
30 |
31 | $this->apiController = new ApiController($schema, new QueryResolver($this->getContainer(), $registry));
32 | $this->context = Context::createDefaultContext();
33 | }
34 |
35 | public function testGenerateUserKey()
36 | {
37 | $query = "
38 | query {
39 | generateSalesChannelKey
40 | }
41 | ";
42 |
43 | $request = $this->createGraphqlRequestRequest($query);
44 | $response = $this->apiController->query($request, $this->context);
45 | static::assertEquals(200, $response->getStatusCode());
46 | $data = json_decode($response->getContent(), true);
47 | static::assertArrayNotHasKey('errors', $data, print_r($data, true));
48 | static::assertNotNull(
49 | $data['data']['generateSalesChannelKey'],
50 | print_r($data['data'], true)
51 | );
52 | }
53 | }
--------------------------------------------------------------------------------
/Test/Actions/GenerateUserKeyActionTest.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(DefinitionRegistry::class);
29 | $schema = SchemaFactory::createSchema($this->getContainer()->get(TypeRegistry::class));
30 |
31 | $this->apiController = new ApiController($schema, new QueryResolver($this->getContainer(), $registry));
32 | $this->context = Context::createDefaultContext();
33 | }
34 |
35 | public function testGenerateUserKey()
36 | {
37 | $query = "
38 | query {
39 | generateUserKey {
40 | accessKey
41 | secretAccessKey
42 | }
43 | }
44 | ";
45 |
46 | $request = $this->createGraphqlRequestRequest($query);
47 | $response = $this->apiController->query($request, $this->context);
48 | static::assertEquals(200, $response->getStatusCode());
49 | $data = json_decode($response->getContent(), true);
50 | static::assertArrayNotHasKey('errors', $data, print_r($data, true));
51 | static::assertArrayHasKey(
52 | 'accessKey',
53 | $data['data']['generateUserKey'],
54 | print_r($data['data'], true)
55 | );
56 | static::assertArrayHasKey(
57 | 'secretAccessKey',
58 | $data['data']['generateUserKey'],
59 | print_r($data['data'], true)
60 | );
61 | }
62 | }
--------------------------------------------------------------------------------
/Test/Actions/ProvideFileNameActionTest.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(DefinitionRegistry::class);
32 | $schema = SchemaFactory::createSchema($this->getContainer()->get(TypeRegistry::class));
33 |
34 | $this->apiController = new ApiController($schema, new QueryResolver($this->getContainer(), $registry));
35 | $this->context = Context::createDefaultContext();
36 | $this->setFixtureContext($this->context);
37 |
38 | $this->repository = $this->getContainer()->get('media.repository');
39 | }
40 |
41 | public function testProvideFileName()
42 | {
43 | $media = $this->getJpg();
44 |
45 | $query = sprintf('
46 | mutation {
47 | provideFileName(
48 | fileName: "%s",
49 | fileExtension: "jpg"
50 | )
51 | }
52 | ', $media->getFileName());
53 |
54 | $request = $this->createGraphqlRequestRequest($query);
55 | $response = $this->apiController->query($request, $this->context);
56 | static::assertEquals(200, $response->getStatusCode());
57 | $data = json_decode($response->getContent(), true);
58 | static::assertArrayNotHasKey('errors', $data, print_r($data, true));
59 | static::assertEquals(
60 | $data['data']['provideFileName'],
61 | $media->getFileName() . '_(1)',
62 | print_r($data['data'], true)
63 | );
64 | }
65 | }
--------------------------------------------------------------------------------
/Test/Actions/RenameMediaActionTest.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(DefinitionRegistry::class);
35 | $schema = SchemaFactory::createSchema($this->getContainer()->get(TypeRegistry::class));
36 |
37 | $this->apiController = new ApiController($schema, new QueryResolver($this->getContainer(), $registry));
38 | $this->context = Context::createDefaultContext();
39 | $this->setFixtureContext($this->context);
40 |
41 | $this->repository = $this->getContainer()->get('media.repository');
42 | }
43 |
44 | public function testRenameMedia()
45 | {
46 | $media = $this->getJpg();
47 |
48 | $urlGenerator = $this->getContainer()->get(UrlGeneratorInterface::class);
49 | $mediaPath = $urlGenerator->getRelativeMediaUrl($media);
50 |
51 | $this->getPublicFilesystem()->put($mediaPath, 'test file');
52 |
53 | $query = sprintf('
54 | mutation {
55 | renameMedia(
56 | mediaId: "%s"
57 | fileName: "new Name"
58 | ) {
59 | id
60 | fileName
61 | }
62 | }
63 | ', $media->getId());
64 |
65 | $request = $this->createGraphqlRequestRequest($query);
66 | $response = $this->apiController->query($request, $this->context);
67 | static::assertEquals(200, $response->getStatusCode());
68 | $data = json_decode($response->getContent(), true);
69 | static::assertArrayNotHasKey('errors', $data, print_r($data, true));
70 | static::assertEquals(
71 | $data['data']['renameMedia']['id'],
72 | $media->getId(),
73 | print_r($data['data'], true)
74 | );
75 | static::assertEquals(
76 | $data['data']['renameMedia']['fileName'],
77 | 'new Name',
78 | print_r($data['data'], true)
79 | );
80 |
81 | /** @var MediaEntity $updatedMedia */
82 | $updatedMedia = $this->repository
83 | ->search(new Criteria([$media->getId()]), $this->context)
84 | ->get($media->getId());
85 | static::assertEquals('new Name', $updatedMedia->getFileName());
86 |
87 | static::assertFalse($this->getPublicFilesystem()->has($mediaPath));
88 | static::assertTrue($this->getPublicFilesystem()->has($urlGenerator->getRelativeMediaUrl($updatedMedia)));
89 | }
90 | }
--------------------------------------------------------------------------------
/Test/Resolver/AssociationResolverTest.php:
--------------------------------------------------------------------------------
1 | [
20 | 'type' => Type::id(),
21 | 'args' => [],
22 | 'fields' => []
23 | ],
24 | 'name' => [
25 | 'type' => Type::string(),
26 | 'args' => [],
27 | 'fields' => []
28 | ]
29 | ], ProductDefinition::class);
30 |
31 | static::assertEmpty($criteria->getAssociations());
32 | }
33 |
34 | public function testAddsToOneAssociation()
35 | {
36 | $criteria = new Criteria();
37 | AssociationResolver::addAssociations($criteria, [
38 | 'id' => [
39 | 'type' => Type::id(),
40 | 'args' => [],
41 | 'fields' => []
42 | ],
43 | 'name' => [
44 | 'type' => Type::string(),
45 | 'args' => [],
46 | 'fields' => []
47 | ],
48 | 'manufacturer' => [
49 | 'type' => new ObjectType(['name' => 'test']),
50 | 'args' => [],
51 | 'fields' => [
52 | 'name' => [
53 | 'type' => Type::string(),
54 | 'args' => [],
55 | 'fields' => []
56 | ]
57 | ]
58 | ]
59 | ], ProductDefinition::class);
60 |
61 | static::assertArrayHasKey('product.manufacturer', $criteria->getAssociations());
62 | static::assertInstanceOf(Criteria::class, $criteria->getAssociations()['product.manufacturer']);
63 | }
64 |
65 | public function testAddsToManyAssociation()
66 | {
67 | $criteria = new Criteria();
68 | AssociationResolver::addAssociations($criteria, [
69 | 'id' => [
70 | 'type' => Type::id(),
71 | 'args' => [],
72 | 'fields' => []
73 | ],
74 | 'name' => [
75 | 'type' => Type::string(),
76 | 'args' => [],
77 | 'fields' => []
78 | ],
79 | 'categories' => [
80 | 'type' => new ObjectType(['name' => 'test']),
81 | 'args' => [],
82 | 'fields' => [
83 | 'name' => [
84 | 'type' => Type::string(),
85 | 'args' => [],
86 | 'fields' => []
87 | ]
88 | ]
89 | ]
90 | ], ProductDefinition::class);
91 |
92 | static::assertArrayHasKey('product.categories', $criteria->getAssociations());
93 | static::assertInstanceOf(Criteria::class, $criteria->getAssociations()['product.categories']);
94 | }
95 |
96 | public function testAddsNestedAssociation()
97 | {
98 | $criteria = new Criteria();
99 | AssociationResolver::addAssociations($criteria, [
100 | 'id' => [
101 | 'type' => Type::id(),
102 | 'args' => [],
103 | 'fields' => []
104 | ],
105 | 'name' => [
106 | 'type' => Type::string(),
107 | 'args' => [],
108 | 'fields' => []
109 | ],
110 | 'categories' => [
111 | 'type' => new ObjectType(['name' => 'test']),
112 | 'args' => [],
113 | 'fields' => [
114 | 'name' => [
115 | 'type' => Type::string(),
116 | 'args' => [],
117 | 'fields' => []
118 | ],
119 | 'parent' => [
120 | 'type' => new ObjectType(['name' => 'test']),
121 | 'args' => [],
122 | 'fields' => [
123 | 'name' => [
124 | 'type' => Type::string(),
125 | 'args' => [],
126 | 'fields' => []
127 | ]
128 | ]
129 | ]
130 | ]
131 | ]
132 | ], ProductDefinition::class);
133 |
134 | static::assertArrayHasKey('product.categories', $criteria->getAssociations());
135 |
136 | /** @var Criteria $nested */
137 | $nested = $criteria->getAssociations()['product.categories'];
138 | static::assertInstanceOf(Criteria::class, $nested);
139 |
140 | static::assertArrayHasKey('category.parent', $nested->getAssociations());
141 | static::assertInstanceOf(Criteria::class, $nested->getAssociations()['category.parent']);
142 | }
143 |
144 | public function testAddsNestedAssociationIgnoresTechnicalFields()
145 | {
146 | $criteria = new Criteria();
147 | AssociationResolver::addAssociations($criteria, [
148 | 'id' => [
149 | 'type' => Type::id(),
150 | 'args' => [],
151 | 'fields' => []
152 | ],
153 | 'name' => [
154 | 'type' => Type::string(),
155 | 'args' => [],
156 | 'fields' => []
157 | ],
158 | 'categories' => [
159 | 'type' => new ObjectType(['name' => 'test']),
160 | 'args' => [],
161 | 'fields' => [
162 | 'edges' => [
163 | 'type' => new ObjectType(['name' => 'test']),
164 | 'args' => [],
165 | 'fields' => [
166 | 'node' => [
167 | 'type' => new ObjectType(['name' => 'test']),
168 | 'args' => [],
169 | 'fields' => [
170 | 'name' => [
171 | 'type' => Type::string(),
172 | 'args' => [],
173 | 'fields' => []
174 | ],
175 | 'parent' => [
176 | 'type' => new ObjectType(['name' => 'test']),
177 | 'args' => [],
178 | 'fields' => [
179 | 'name' => [
180 | 'type' => Type::string(),
181 | 'args' => [],
182 | 'fields' => []
183 | ]
184 | ]
185 | ]
186 | ]
187 | ]
188 | ]
189 | ]
190 |
191 | ]
192 | ]
193 | ], ProductDefinition::class);
194 |
195 | static::assertArrayHasKey('product.categories', $criteria->getAssociations());
196 |
197 | /** @var Criteria $nested */
198 | $nested = $criteria->getAssociations()['product.categories'];
199 | static::assertInstanceOf(Criteria::class, $nested);
200 |
201 | static::assertArrayHasKey('category.parent', $nested->getAssociations());
202 | static::assertInstanceOf(Criteria::class, $nested->getAssociations()['category.parent']);
203 | }
204 |
205 | public function testAddsNestedAssociationWithArgs()
206 | {
207 | $criteria = new Criteria();
208 | AssociationResolver::addAssociations($criteria, [
209 | 'id' => [
210 | 'type' => Type::id(),
211 | 'args' => [],
212 | 'fields' => []
213 | ],
214 | 'name' => [
215 | 'type' => Type::string(),
216 | 'args' => [],
217 | 'fields' => []
218 | ],
219 | 'categories' => [
220 | 'type' => new ObjectType(['name' => 'test']),
221 | 'args' => [
222 | 'sortBy' => 'name',
223 | 'sortDirection' => 'DESC'
224 | ],
225 | 'fields' => [
226 | 'edges' => [
227 | 'type' => new ObjectType(['name' => 'test']),
228 | 'args' => [],
229 | 'fields' => [
230 | 'node' => [
231 | 'type' => new ObjectType(['name' => 'test']),
232 | 'args' => [],
233 | 'fields' => [
234 | 'name' => [
235 | 'type' => Type::string(),
236 | 'args' => [],
237 | 'fields' => []
238 | ],
239 | 'parent' => [
240 | 'type' => new ObjectType(['name' => 'test']),
241 | 'args' => [],
242 | 'fields' => [
243 | 'name' => [
244 | 'type' => Type::string(),
245 | 'args' => [],
246 | 'fields' => []
247 | ]
248 | ]
249 | ]
250 | ]
251 | ]
252 | ]
253 | ]
254 |
255 | ]
256 | ]
257 | ], ProductDefinition::class);
258 |
259 | static::assertArrayHasKey('product.categories', $criteria->getAssociations());
260 |
261 | /** @var Criteria $nested */
262 | $nested = $criteria->getAssociations()['product.categories'];
263 | static::assertInstanceOf(Criteria::class, $nested);
264 | static::assertEquals('name', $nested->getSorting()[0]->getField());
265 | static::assertEquals(FieldSorting::DESCENDING, $nested->getSorting()[0]->getDirection());
266 |
267 | static::assertArrayHasKey('category.parent', $nested->getAssociations());
268 | static::assertInstanceOf(Criteria::class, $nested->getAssociations()['category.parent']);
269 | }
270 | }
--------------------------------------------------------------------------------
/Test/Resolver/CriteriaParserTest.php:
--------------------------------------------------------------------------------
1 | 5,
29 | 'after' => base64_encode('10')
30 | ],
31 | ProductDefinition::class
32 | );
33 |
34 | static::assertEquals(Criteria::TOTAL_COUNT_MODE_EXACT, $criteria->getTotalCountMode());
35 | static::assertEquals(5, $criteria->getLimit());
36 | static::assertEquals(10, $criteria->getOffset());
37 | }
38 |
39 | public function testParsePaginationBackward()
40 | {
41 | $criteria = CriteriaParser::buildCriteria([
42 | 'last' => 5,
43 | 'before' => base64_encode('15')
44 | ],
45 | ProductDefinition::class
46 | );
47 |
48 | static::assertEquals(Criteria::TOTAL_COUNT_MODE_EXACT, $criteria->getTotalCountMode());
49 | static::assertEquals(5, $criteria->getLimit());
50 | static::assertEquals(10, $criteria->getOffset());
51 | }
52 |
53 | public function testParseSorting()
54 | {
55 | $criteria = CriteriaParser::buildCriteria([
56 | 'sortBy' => 'id',
57 | 'sortDirection' => FieldSorting::DESCENDING
58 | ],
59 | ProductDefinition::class
60 | );
61 |
62 | static::assertEquals('id', $criteria->getSorting()[0]->getField());
63 | static::assertEquals(FieldSorting::DESCENDING, $criteria->getSorting()[0]->getDirection());
64 | }
65 |
66 | public function testParseEqualsQuery()
67 | {
68 | $criteria = CriteriaParser::buildCriteria([
69 | 'query' => [
70 | 'type' => 'equals',
71 | 'field' => 'id',
72 | 'value' => 'test'
73 | ]
74 | ],
75 | ProductDefinition::class
76 | );
77 |
78 | static::assertInstanceOf(EqualsFilter::class, $criteria->getFilters()[0]);
79 | static::assertEquals('product.id', $criteria->getFilters()[0]->getField());
80 | static::assertEquals('test', $criteria->getFilters()[0]->getValue());
81 | }
82 |
83 | public function testParseEqualsAnyQuery()
84 | {
85 | $criteria = CriteriaParser::buildCriteria([
86 | 'query' => [
87 | 'type' => 'equalsAny',
88 | 'field' => 'id',
89 | 'value' => 'test|fancy'
90 | ]
91 | ],
92 | ProductDefinition::class
93 | );
94 |
95 | static::assertInstanceOf(EqualsAnyFilter::class, $criteria->getFilters()[0]);
96 | static::assertEquals('product.id', $criteria->getFilters()[0]->getField());
97 | static::assertEquals('test', $criteria->getFilters()[0]->getValue()[0]);
98 | static::assertEquals('fancy', $criteria->getFilters()[0]->getValue()[1]);
99 | }
100 |
101 | public function testParseContainsQuery()
102 | {
103 | $criteria = CriteriaParser::buildCriteria([
104 | 'query' => [
105 | 'type' => 'contains',
106 | 'field' => 'id',
107 | 'value' => 'test'
108 | ]
109 | ],
110 | ProductDefinition::class
111 | );
112 |
113 | static::assertInstanceOf(ContainsFilter::class, $criteria->getFilters()[0]);
114 | static::assertEquals('product.id', $criteria->getFilters()[0]->getField());
115 | static::assertEquals('test', $criteria->getFilters()[0]->getValue());
116 | }
117 |
118 | public function testParseRangeQuery()
119 | {
120 | $criteria = CriteriaParser::buildCriteria([
121 | 'query' => [
122 | 'type' => 'range',
123 | 'field' => 'id',
124 | 'parameters' => [
125 | [
126 | 'operator' => 'gt',
127 | 'value' => 5
128 | ],
129 | [
130 | 'operator' => 'lt',
131 | 'value' => 10
132 | ],
133 | ]
134 | ]
135 | ],
136 | ProductDefinition::class
137 | );
138 |
139 | static::assertInstanceOf(RangeFilter::class, $criteria->getFilters()[0]);
140 | static::assertEquals('product.id', $criteria->getFilters()[0]->getField());
141 | static::assertEquals(5, $criteria->getFilters()[0]->getParameter('gt'));
142 | static::assertEquals(10, $criteria->getFilters()[0]->getParameter('lt'));
143 | }
144 |
145 | public function testParseNotQuery()
146 | {
147 | $criteria = CriteriaParser::buildCriteria([
148 | 'query' => [
149 | 'type' => 'not',
150 | 'operator' => 'AND',
151 | 'queries' => [
152 | [
153 | 'type' => 'equals',
154 | 'field' => 'id',
155 | 'value' => 'test'
156 | ]
157 | ]
158 | ]
159 | ],
160 | ProductDefinition::class
161 | );
162 |
163 | static::assertInstanceOf(NotFilter::class, $criteria->getFilters()[0]);
164 | static::assertEquals('AND', $criteria->getFilters()[0]->getOperator());
165 |
166 | $inner = $criteria->getFilters()[0]->getQueries()[0];
167 | static::assertInstanceOf(EqualsFilter::class, $inner);
168 | static::assertEquals('product.id', $inner->getField());
169 | static::assertEquals('test', $inner->getValue());
170 | }
171 |
172 | public function testParseMultiQuery()
173 | {
174 | $criteria = CriteriaParser::buildCriteria([
175 | 'query' => [
176 | 'type' => 'multi',
177 | 'operator' => 'AND',
178 | 'queries' => [
179 | [
180 | 'type' => 'equals',
181 | 'field' => 'id',
182 | 'value' => 'test'
183 | ],
184 | [
185 | 'type' => 'equalsAny',
186 | 'field' => 'id',
187 | 'value' => 'test|fancy'
188 | ]
189 | ]
190 | ]
191 | ],
192 | ProductDefinition::class
193 | );
194 |
195 | static::assertInstanceOf(MultiFilter::class, $criteria->getFilters()[0]);
196 | static::assertEquals('AND', $criteria->getFilters()[0]->getOperator());
197 |
198 | $first = $criteria->getFilters()[0]->getQueries()[0];
199 | static::assertInstanceOf(EqualsFilter::class, $first);
200 | static::assertEquals('product.id', $first->getField());
201 | static::assertEquals('test', $first->getValue());
202 |
203 | $second = $criteria->getFilters()[0]->getQueries()[1];
204 | static::assertInstanceOf(EqualsAnyFilter::class, $second);
205 | static::assertEquals('product.id', $second->getField());
206 | static::assertEquals('test', $second->getValue()[0]);
207 | static::assertEquals('fancy', $second->getValue()[1]);
208 | }
209 |
210 | public function testParseMaxAggregation()
211 | {
212 | $criteria = CriteriaParser::buildCriteria([
213 | 'aggregations' => [
214 | [
215 | 'type' => 'max',
216 | 'field' => 'id',
217 | 'name' => 'max_id'
218 | ]
219 | ]
220 | ],
221 | ProductDefinition::class
222 | );
223 |
224 | static::assertInstanceOf(MaxAggregation::class, $criteria->getAggregations()['max_id']);
225 | static::assertEquals('product.id', $criteria->getAggregations()['max_id']->getField());
226 | static::assertEquals('max_id', $criteria->getAggregations()['max_id']->getName());
227 | }
228 |
229 | public function testParseMinAggregation()
230 | {
231 | $criteria = CriteriaParser::buildCriteria([
232 | 'aggregations' => [
233 | [
234 | 'type' => 'min',
235 | 'field' => 'id',
236 | 'name' => 'min_id'
237 | ]
238 | ]
239 | ],
240 | ProductDefinition::class
241 | );
242 |
243 | static::assertInstanceOf(MinAggregation::class, $criteria->getAggregations()['min_id']);
244 | static::assertEquals('product.id', $criteria->getAggregations()['min_id']->getField());
245 | static::assertEquals('min_id', $criteria->getAggregations()['min_id']->getName());
246 | }
247 |
248 | public function testParseAvgAggregation()
249 | {
250 | $criteria = CriteriaParser::buildCriteria([
251 | 'aggregations' => [
252 | [
253 | 'type' => 'avg',
254 | 'field' => 'id',
255 | 'name' => 'avg_id'
256 | ]
257 | ]
258 | ],
259 | ProductDefinition::class
260 | );
261 |
262 | static::assertInstanceOf(AvgAggregation::class, $criteria->getAggregations()['avg_id']);
263 | static::assertEquals('product.id', $criteria->getAggregations()['avg_id']->getField());
264 | static::assertEquals('avg_id', $criteria->getAggregations()['avg_id']->getName());
265 | }
266 |
267 | public function testParseCountAggregation()
268 | {
269 | $criteria = CriteriaParser::buildCriteria([
270 | 'aggregations' => [
271 | [
272 | 'type' => 'count',
273 | 'field' => 'id',
274 | 'name' => 'count_id'
275 | ]
276 | ]
277 | ],
278 | ProductDefinition::class
279 | );
280 |
281 | static::assertInstanceOf(CountAggregation::class, $criteria->getAggregations()['count_id']);
282 | static::assertEquals('product.id', $criteria->getAggregations()['count_id']->getField());
283 | static::assertEquals('count_id', $criteria->getAggregations()['count_id']->getName());
284 | }
285 |
286 | public function testParseValueCountAggregation()
287 | {
288 | $criteria = CriteriaParser::buildCriteria([
289 | 'aggregations' => [
290 | [
291 | 'type' => 'value_count',
292 | 'field' => 'id',
293 | 'name' => 'value_count_id'
294 | ]
295 | ]
296 | ],
297 | ProductDefinition::class
298 | );
299 |
300 | static::assertInstanceOf(ValueCountAggregation::class, $criteria->getAggregations()['value_count_id']);
301 | static::assertEquals('product.id', $criteria->getAggregations()['value_count_id']->getField());
302 | static::assertEquals('value_count_id', $criteria->getAggregations()['value_count_id']->getName());
303 | }
304 |
305 | public function testParseStatsAggregation()
306 | {
307 | $criteria = CriteriaParser::buildCriteria([
308 | 'aggregations' => [
309 | [
310 | 'type' => 'stats',
311 | 'field' => 'id',
312 | 'name' => 'stats_id'
313 | ]
314 | ]
315 | ],
316 | ProductDefinition::class
317 | );
318 |
319 | static::assertInstanceOf(StatsAggregation::class, $criteria->getAggregations()['stats_id']);
320 | static::assertEquals('product.id', $criteria->getAggregations()['stats_id']->getField());
321 | static::assertEquals('stats_id', $criteria->getAggregations()['stats_id']->getName());
322 | }
323 | }
--------------------------------------------------------------------------------
/Test/Resolver/Struct/AggregationStructTest.php:
--------------------------------------------------------------------------------
1 | null, 'avg' => 14]])
21 | );
22 |
23 | static::assertEquals('name', $aggregation->getName());
24 | static::assertCount(1, $aggregation->getBuckets());
25 | $bucket = $aggregation->getBuckets()[0];
26 | static::assertEquals([], $bucket->getKeys());
27 | static::assertCount(1, $bucket->getResults());
28 | $result = $bucket->getResults()[0];
29 | static::assertEquals('avg', $result->getType());
30 | static::assertEquals(14, $result->getResult());
31 | }
32 |
33 | public function testFromAggregationResultStatsAggregation()
34 | {
35 | $aggregation = AggregationStruct::fromAggregationResult(
36 | new AggregationResult(
37 | new StatsAggregation('field', 'name'), [
38 | [
39 | 'key' => null,
40 | 'count' => 1,
41 | 'avg' => 2.0,
42 | 'sum' => 3.0,
43 | 'min' => 4.0,
44 | 'max' => 5.0
45 | ]
46 | ]
47 | ));
48 |
49 | static::assertEquals('name', $aggregation->getName());
50 | static::assertCount(1, $aggregation->getBuckets());
51 | $bucket = $aggregation->getBuckets()[0];
52 | static::assertEquals([], $bucket->getKeys());
53 | static::assertCount(5, $bucket->getResults());
54 | static::assertEquals('count', $bucket->getResults()[0]->getType());
55 | static::assertEquals(1, $bucket->getResults()[0]->getResult());
56 | static::assertEquals('avg', $bucket->getResults()[1]->getType());
57 | static::assertEquals(2.0, $bucket->getResults()[1]->getResult());
58 | static::assertEquals('sum', $bucket->getResults()[2]->getType());
59 | static::assertEquals(3.0, $bucket->getResults()[2]->getResult());
60 | static::assertEquals('min', $bucket->getResults()[3]->getType());
61 | static::assertEquals(4.0, $bucket->getResults()[3]->getResult());
62 | static::assertEquals('max', $bucket->getResults()[4]->getType());
63 | static::assertEquals(5.0, $bucket->getResults()[4]->getResult());
64 | }
65 |
66 | public function testFromAggregationResultValueCountAggregation()
67 | {
68 | $aggregation = AggregationStruct::fromAggregationResult(
69 | new AggregationResult(
70 | new ValueCountAggregation('field', 'name'), [
71 | [
72 | 'key' => null,
73 | 'values' => [
74 | 'test' => 1,
75 | 'another' => 2
76 | ]
77 | ]
78 | ]
79 | ));
80 |
81 | static::assertEquals('name', $aggregation->getName());
82 | static::assertCount(1, $aggregation->getBuckets());
83 | $bucket = $aggregation->getBuckets()[0];
84 | static::assertEquals([], $bucket->getKeys());
85 | static::assertCount(2, $bucket->getResults());
86 | static::assertEquals('test', $bucket->getResults()[0]->getType());
87 | static::assertEquals(1, $bucket->getResults()[0]->getResult());
88 | static::assertEquals('another', $bucket->getResults()[1]->getType());
89 | static::assertEquals(2, $bucket->getResults()[1]->getResult());
90 | }
91 |
92 | public function testFromAggregationResultValueAggregation()
93 | {
94 | $aggregation = AggregationStruct::fromAggregationResult(
95 | new AggregationResult(
96 | new ValueAggregation('field', 'name'), [
97 | [
98 | 'key' => null,
99 | 'values' => [
100 | 'test',
101 | 'another'
102 | ]
103 | ]
104 | ]
105 | ));
106 |
107 | static::assertEquals('name', $aggregation->getName());
108 | static::assertCount(1, $aggregation->getBuckets());
109 | $bucket = $aggregation->getBuckets()[0];
110 | static::assertEquals([], $bucket->getKeys());
111 | static::assertCount(2, $bucket->getResults());
112 | static::assertEquals('0', $bucket->getResults()[0]->getType());
113 | static::assertEquals('test', $bucket->getResults()[0]->getResult());
114 | static::assertEquals('1', $bucket->getResults()[1]->getType());
115 | static::assertEquals('another', $bucket->getResults()[1]->getResult());
116 | }
117 |
118 | public function testFromAggregationResultCollection()
119 | {
120 | $aggregations = AggregationStruct::fromCollection(new AggregationResultCollection([
121 | new AggregationResult(new MaxAggregation('field', 'max'), [['key' => null, 'max' => 20]]),
122 | new AggregationResult(new AvgAggregation('field', 'avg'), [['key' => null, 'avg' => 14]])
123 | ]));
124 |
125 | static::assertCount(2, $aggregations);
126 | static::assertEquals('max', $aggregations[0]->getName());
127 | static::assertCount(1, $aggregations[0]->getBuckets());
128 | $bucket = $aggregations[0]->getBuckets()[0];
129 | static::assertEquals([], $bucket->getKeys());
130 | static::assertCount(1, $bucket->getResults());
131 | static::assertEquals('max', $bucket->getResults()[0]->getType());
132 | static::assertEquals(20, $bucket->getResults()[0]->getResult());
133 |
134 | static::assertEquals('avg', $aggregations[1]->getName());
135 | static::assertCount(1, $aggregations[1]->getBuckets());
136 | $bucket = $aggregations[1]->getBuckets()[0];
137 | static::assertEquals([], $bucket->getKeys());
138 | static::assertCount(1, $bucket->getResults());
139 | static::assertEquals('avg', $bucket->getResults()[0]->getType());
140 | static::assertEquals(14, $bucket->getResults()[0]->getResult());
141 | }
142 |
143 | }
--------------------------------------------------------------------------------
/Test/Resolver/Struct/ConnectionStructTest.php:
--------------------------------------------------------------------------------
1 | setId('1');
23 | $entity2 = new ProductEntity();
24 | $entity2->setId('2');
25 | $criteria = new Criteria;
26 | $criteria->setLimit(10);
27 | $criteria->setOffset(5);
28 |
29 | $result = new EntitySearchResult(
30 | 100,
31 | new EntityCollection([$entity1, $entity2]),
32 | new AggregationResultCollection([
33 | new AggregationResult(new MaxAggregation('field', 'max'), [['key' => null, 'max' => 20]]),
34 | new AggregationResult(new AvgAggregation('field', 'avg'), [['key' => null, 'avg' => 14]])
35 | ]),
36 | $criteria,
37 | Context::createDefaultContext()
38 | );
39 | $connection = ConnectionStruct::fromResult($result);
40 |
41 | static::assertEquals(100, $connection->getTotal());
42 |
43 | static::assertCount(2, $connection->getEdges());
44 | static::assertEquals($entity1, $connection->getEdges()[0]->getNode());
45 | static::assertEquals(base64_encode('6'), $connection->getEdges()[0]->getCursor());
46 | static::assertEquals($entity2, $connection->getEdges()[1]->getNode());
47 | static::assertEquals(base64_encode('7'), $connection->getEdges()[1]->getCursor());
48 |
49 | $aggregations = $connection->getAggregations();
50 | static::assertCount(2, $aggregations);
51 | static::assertEquals('max', $aggregations[0]->getName());
52 | static::assertCount(1, $aggregations[0]->getBuckets());
53 | $bucket = $aggregations[0]->getBuckets()[0];
54 | static::assertEquals([], $bucket->getKeys());
55 | static::assertCount(1, $bucket->getResults());
56 | static::assertEquals('max', $bucket->getResults()[0]->getType());
57 | static::assertEquals(20, $bucket->getResults()[0]->getResult());
58 |
59 | static::assertEquals('avg', $aggregations[1]->getName());
60 | static::assertCount(1, $aggregations[1]->getBuckets());
61 | $bucket = $aggregations[1]->getBuckets()[0];
62 | static::assertEquals([], $bucket->getKeys());
63 | static::assertCount(1, $bucket->getResults());
64 | static::assertEquals('avg', $bucket->getResults()[0]->getType());
65 | static::assertEquals(14, $bucket->getResults()[0]->getResult());
66 |
67 | static::assertTrue($connection->getPageInfo()->getHasNextPage());
68 | static::assertEquals(base64_encode('15'), $connection->getPageInfo()->getEndCursor());
69 | static::assertTrue($connection->getPageInfo()->getHasPreviousPage());
70 | static::assertEquals(base64_encode('6'), $connection->getPageInfo()->getStartCursor());
71 | }
72 | }
--------------------------------------------------------------------------------
/Test/Resolver/Struct/EdgeStructTest.php:
--------------------------------------------------------------------------------
1 | assign(['id' => 1]);
14 | $entity2 = (new Entity())->assign(['id' => 2]);
15 |
16 | $edges = EdgeStruct::fromElements(
17 | [
18 | $entity1,
19 | $entity2
20 | ],
21 | 10
22 | );
23 |
24 | static::assertCount(2, $edges);
25 |
26 | static::assertEquals($entity1, $edges[0]->getNode());
27 | static::assertEquals(base64_encode('11'), $edges[0]->getCursor());
28 |
29 | static::assertEquals($entity2, $edges[1]->getNode());
30 | static::assertEquals(base64_encode('12'), $edges[1]->getCursor());
31 | }
32 | }
--------------------------------------------------------------------------------
/Test/Resolver/Struct/PageInfoStructTest.php:
--------------------------------------------------------------------------------
1 | setLimit(10);
15 | $criteria->setOffset(5);
16 |
17 | $pageInfo = PageInfoStruct::fromCriteria($criteria, 100);
18 |
19 | static::assertTrue($pageInfo->getHasNextPage());
20 | static::assertTrue($pageInfo->getHasPreviousPage());
21 | static::assertEquals(base64_encode('15'), $pageInfo->getEndCursor());
22 | static::assertEquals(base64_encode('6'), $pageInfo->getStartCursor());
23 | }
24 | }
--------------------------------------------------------------------------------
/Test/Schema/CustomTypesTest.php:
--------------------------------------------------------------------------------
1 | customTypes = new CustomTypes();
20 | }
21 |
22 | public function testDate()
23 | {
24 | static::assertInstanceOf(DateType::class, $this->customTypes->date());
25 | $this->customTypes->date()->assertValid();
26 | }
27 |
28 | public function testJson()
29 | {
30 | static::assertInstanceOf(JsonType::class, $this->customTypes->json());
31 | $this->customTypes->json()->assertValid();
32 | }
33 |
34 | public function testSortDirection()
35 | {
36 | static::assertInstanceOf(EnumType::class, $this->customTypes->sortDirection());
37 | $this->customTypes->sortDirection()->assertValid();
38 | }
39 |
40 | public function testQueryOperator()
41 | {
42 | static::assertInstanceOf(EnumType::class, $this->customTypes->queryOperator());
43 | $this->customTypes->queryOperator()->assertValid();
44 | }
45 |
46 | public function testRangeOperator()
47 | {
48 | static::assertInstanceOf(EnumType::class, $this->customTypes->rangeOperator());
49 | $this->customTypes->rangeOperator()->assertValid();
50 | }
51 |
52 | public function testQueryTypes()
53 | {
54 | static::assertInstanceOf(EnumType::class, $this->customTypes->queryTypes());
55 | $this->customTypes->queryTypes()->assertValid();
56 | }
57 |
58 | public function testAggregationTypes()
59 | {
60 | static::assertInstanceOf(EnumType::class, $this->customTypes->aggregationTypes());
61 | $this->customTypes->aggregationTypes()->assertValid();
62 | }
63 |
64 | public function testPageInfo()
65 | {
66 | static::assertInstanceOf(ObjectType::class, $this->customTypes->pageInfo());
67 | $this->customTypes->pageInfo()->assertValid();
68 | }
69 |
70 | public function testAggregationResult()
71 | {
72 | static::assertInstanceOf(ObjectType::class, $this->customTypes->aggregationResult());
73 | $this->customTypes->aggregationResult()->assertValid();
74 | }
75 |
76 | public function testQuery()
77 | {
78 | static::assertInstanceOf(InputObjectType::class, $this->customTypes->query());
79 | $this->customTypes->query()->assertValid();
80 | }
81 |
82 | public function testAggregation()
83 | {
84 | static::assertInstanceOf(InputObjectType::class, $this->customTypes->aggregation());
85 | $this->customTypes->aggregation()->assertValid();
86 | }
87 | }
--------------------------------------------------------------------------------
/Test/Schema/MutationTest.php:
--------------------------------------------------------------------------------
1 | getName());
15 | }
16 |
17 | public function testGetNameUpdate()
18 | {
19 | $mutation = new Mutation(Mutation::ACTION_UPDATE, 'test');
20 |
21 | static::assertEquals('updateTest', $mutation->getName());
22 | }
23 |
24 | public function testGetNameDelete()
25 | {
26 | $mutation = new Mutation(Mutation::ACTION_DELETE, 'test');
27 |
28 | static::assertEquals('deleteTest', $mutation->getName());
29 | }
30 |
31 | public function testFromNameCreate()
32 | {
33 | $mutation = Mutation::fromName('createTest');
34 |
35 | static::assertEquals(Mutation::ACTION_CREATE, $mutation->getAction());
36 | static::assertEquals('test', $mutation->getEntityName());
37 | }
38 |
39 | public function testFromNameUpdate()
40 | {
41 | $mutation = Mutation::fromName('updateTest');
42 |
43 | static::assertEquals(Mutation::ACTION_UPDATE, $mutation->getAction());
44 | static::assertEquals('test', $mutation->getEntityName());
45 | }
46 |
47 | public function testFromNameDelete()
48 | {
49 | $mutation = Mutation::fromName('deleteTest');
50 |
51 | static::assertEquals(Mutation::ACTION_DELETE, $mutation->getAction());
52 | static::assertEquals('test', $mutation->getEntityName());
53 | }
54 | }
--------------------------------------------------------------------------------
/Test/Schema/SchemaFactoryTest.php:
--------------------------------------------------------------------------------
1 | getContainer()->get(TypeRegistry::class));
18 |
19 | static::assertInstanceOf(Schema::class, $schema);
20 | static::assertEmpty($schema->validate());
21 | }
22 | }
--------------------------------------------------------------------------------
/Test/Schema/TypeRegistryTest.php:
--------------------------------------------------------------------------------
1 | definitionRegistry = $this->createMock(DefinitionRegistry::class);
48 | $this->typeRegistry = new TypeRegistry(
49 | $this->definitionRegistry,
50 | new CustomTypes(),
51 | new CustomFieldRegistry(),
52 | new CustomFieldRegistry()
53 | );
54 | }
55 |
56 | public function testGetQueryForBaseEntity()
57 | {
58 | $this->definitionRegistry->expects($this->once())
59 | ->method('getDefinitions')
60 | ->willReturn([BaseEntity::class]);
61 |
62 | $query = $this->typeRegistry->getQuery();
63 | static::assertInstanceOf(ObjectType::class, $query);
64 | static::assertEquals('Query', $query->name);
65 | static::assertCount(2, $query->getFields());
66 |
67 | $expectedFields = [
68 | 'id' => NonNull::class,
69 | 'bool' => BooleanType::class,
70 | 'date' => DateType::class,
71 | 'int' => IntType::class,
72 | 'float' => FloatType::class,
73 | 'json' => JsonType::class,
74 | 'string' => StringType::class
75 | ];
76 |
77 | $fieldName = Inflector::camelize(BaseEntity::getEntityName());
78 | $baseField = $query->getField($fieldName);
79 | static::assertObject($expectedFields, $baseField->getType());
80 | static::assertInputArgs([
81 | 'id' => NonNull::class,
82 | ], $baseField);
83 |
84 | $pluralizedName = Inflector::pluralize($fieldName);
85 | $baseField = $query->getField($pluralizedName);
86 | static::assertConnectionObject($expectedFields, $baseField->getType());
87 | static::assertConnectionArgs($baseField);
88 | }
89 |
90 | public function testGetQueryForAssociationEntity()
91 | {
92 | $this->definitionRegistry->expects($this->once())
93 | ->method('getDefinitions')
94 | ->willReturn([AssociationEntity::class, ManyToManyEntity::class, ManyToOneEntity::class, MappingEntity::class]);
95 |
96 | $query = $this->typeRegistry->getQuery();
97 | static::assertInstanceOf(ObjectType::class, $query);
98 | static::assertEquals('Query', $query->name);
99 | static::assertCount(6, $query->getFields());
100 |
101 | $expectedFields = [
102 | 'manyToMany' => ObjectType::class,
103 | 'manyToOneId' => IDType::class,
104 | 'manyToOne' => ObjectType::class
105 | ];
106 | $fieldName = Inflector::camelize(AssociationEntity::getEntityName());
107 | $associationField = $query->getField($fieldName);
108 | static::assertObject($expectedFields, $associationField->getType());
109 |
110 | $pluralizedName = Inflector::pluralize($fieldName);
111 | $associationField = $query->getField($pluralizedName);
112 | static::assertConnectionObject($expectedFields, $associationField->getType());
113 |
114 | $expectedFields = [
115 | 'association' => ObjectType::class,
116 | ];
117 |
118 | $fieldName = Inflector::camelize(ManyToManyEntity::getEntityName());
119 | $manyToManyField = $query->getField($fieldName);
120 | static::assertObject($expectedFields, $manyToManyField->getType());
121 |
122 | $pluralizedName = Inflector::pluralize($fieldName);
123 | $manyToManyField = $query->getField($pluralizedName);
124 | static::assertConnectionObject($expectedFields, $manyToManyField->getType());
125 |
126 | $expectedFields = [
127 | 'association' => ObjectType::class,
128 | ];
129 |
130 | $fieldName = Inflector::camelize(ManyToOneEntity::getEntityName());
131 | $manyToOneField = $query->getField($fieldName);
132 | static::assertObject($expectedFields, $manyToOneField->getType());
133 |
134 | $pluralizedName = Inflector::pluralize($fieldName);
135 | $manyToOneField = $query->getField($pluralizedName);
136 | static::assertConnectionObject($expectedFields, $manyToOneField->getType());
137 | }
138 |
139 | public function testGetQueryIgnoresTranslationEntity()
140 | {
141 | $this->definitionRegistry->expects($this->once())
142 | ->method('getDefinitions')
143 | ->willReturn([ProductTranslationDefinition::class]);
144 |
145 | $query = $this->typeRegistry->getQuery();
146 | static::assertInstanceOf(ObjectType::class, $query);
147 | static::assertEquals('Query', $query->name);
148 | static::assertCount(0, $query->getFields());
149 | }
150 |
151 | public function testGetQueryIgnoresMappingEntity()
152 | {
153 | $this->definitionRegistry->expects($this->once())
154 | ->method('getDefinitions')
155 | ->willReturn([ProductCategoryDefinition::class]);
156 |
157 | $query = $this->typeRegistry->getQuery();
158 | static::assertInstanceOf(ObjectType::class, $query);
159 | static::assertEquals('Query', $query->name);
160 | static::assertCount(0, $query->getFields());
161 | }
162 |
163 | public function testGetMutationForBaseEntity()
164 | {
165 | $this->definitionRegistry->expects($this->once())
166 | ->method('getDefinitions')
167 | ->willReturn([BaseEntity::class]);
168 |
169 | $query = $this->typeRegistry->getMutation();
170 | static::assertInstanceOf(ObjectType::class, $query);
171 | static::assertEquals('Mutation', $query->name);
172 | static::assertCount(3, $query->getFields());
173 |
174 | $create = new Mutation(Mutation::ACTION_CREATE, BaseEntity::getEntityName());
175 | $createField = $query->getField($create->getName());
176 | static::assertObject([
177 | 'id' => NonNull::class,
178 | 'bool' => BooleanType::class,
179 | 'date' => DateType::class,
180 | 'int' => IntType::class,
181 | 'float' => FloatType::class,
182 | 'json' => JsonType::class,
183 | 'string' => StringType::class
184 | ], $createField->getType());
185 |
186 | static::assertInputArgs([
187 | 'id' => IDType::class,
188 | 'bool' => BooleanType::class,
189 | 'date' => DateType::class,
190 | 'int' => IntType::class,
191 | 'float' => FloatType::class,
192 | 'json' => JsonType::class,
193 | 'string' => StringType::class
194 | ], $createField);
195 |
196 | $update = new Mutation(Mutation::ACTION_UPDATE, BaseEntity::getEntityName());
197 | $updateField = $query->getField($update->getName());
198 | static::assertObject([
199 | 'id' => NonNull::class,
200 | 'bool' => BooleanType::class,
201 | 'date' => DateType::class,
202 | 'int' => IntType::class,
203 | 'float' => FloatType::class,
204 | 'json' => JsonType::class,
205 | 'string' => StringType::class
206 | ], $updateField->getType());
207 |
208 | static::assertInputArgs([
209 | 'id' => NonNull::class,
210 | 'bool' => BooleanType::class,
211 | 'date' => DateType::class,
212 | 'int' => IntType::class,
213 | 'float' => FloatType::class,
214 | 'json' => JsonType::class,
215 | 'string' => StringType::class
216 | ], $updateField);
217 |
218 | $delete = new Mutation(Mutation::ACTION_DELETE, BaseEntity::getEntityName());
219 | $deleteField = $query->getField($delete->getName());
220 | static::assertInstanceOf(IDType::class, $deleteField->getType());
221 | static::assertCount(1, $deleteField->args);
222 | static::assertInstanceOf(NonNull::class, $deleteField->getArg('id')->getType());
223 | }
224 |
225 | public function testGetMutationForAssociationEntity()
226 | {
227 | $this->definitionRegistry->expects($this->once())
228 | ->method('getDefinitions')
229 | ->willReturn([AssociationEntity::class, ManyToManyEntity::class, ManyToOneEntity::class, MappingEntity::class]);
230 |
231 | $query = $this->typeRegistry->getMutation();
232 | static::assertInstanceOf(ObjectType::class, $query);
233 | static::assertEquals('Mutation', $query->name);
234 | static::assertCount(9, $query->getFields());
235 |
236 | $association = new Mutation(Mutation::ACTION_CREATE, AssociationEntity::getEntityName());
237 | $associationField = $query->getField($association->getName());
238 | static::assertObject([
239 | 'manyToMany' => ObjectType::class,
240 | 'manyToOneId' => IDType::class,
241 | 'manyToOne' => ObjectType::class
242 | ], $associationField->getType());
243 | static::assertConnectionObject([
244 | 'association' => ObjectType::class,
245 | ], $associationField->getType()->getField('manyToMany')->getType());
246 | static::assertInputArgs([
247 | 'id' => IDType::class,
248 | 'manyToMany' => ListOfType::class,
249 | 'manyToOneId' => IDType::class,
250 | 'manyToOne' => InputObjectType::class
251 | ], $associationField);
252 |
253 | $association = new Mutation(Mutation::ACTION_UPDATE, AssociationEntity::getEntityName());
254 | $associationField = $query->getField($association->getName());
255 | static::assertObject([
256 | 'manyToMany' => ObjectType::class,
257 | 'manyToOneId' => IDType::class,
258 | 'manyToOne' => ObjectType::class
259 | ], $associationField->getType());
260 | static::assertConnectionObject([
261 | 'association' => ObjectType::class,
262 | ], $associationField->getType()->getField('manyToMany')->getType());
263 | static::assertInputArgs([
264 | 'id' => NonNull::class,
265 | 'manyToMany' => ListOfType::class,
266 | 'manyToOneId' => IDType::class,
267 | 'manyToOne' => InputObjectType::class
268 | ], $associationField);
269 |
270 | $delete = new Mutation(Mutation::ACTION_DELETE, AssociationEntity::getEntityName());
271 | $deleteField = $query->getField($delete->getName());
272 | static::assertInstanceOf(IDType::class, $deleteField->getType());
273 | static::assertCount(1, $deleteField->args);
274 | static::assertInstanceOf(NonNull::class, $deleteField->getArg('id')->getType());
275 |
276 | $manyToMany = new Mutation(Mutation::ACTION_CREATE, ManyToManyEntity::getEntityName());
277 | $manyToManyField = $query->getField($manyToMany->getName());
278 | static::assertObject([
279 | 'association' => ObjectType::class,
280 | ], $manyToManyField->getType());
281 | static::assertConnectionObject([
282 | 'manyToMany' => ObjectType::class,
283 | 'manyToOneId' => IDType::class,
284 | 'manyToOne' => ObjectType::class
285 | ], $manyToManyField->getType()->getField('association')->getType());
286 | static::assertInputArgs([
287 | 'id' => IDType::class,
288 | 'association' => ListOfType::class,
289 | ], $manyToManyField);
290 |
291 | $manyToMany = new Mutation(Mutation::ACTION_UPDATE, ManyToManyEntity::getEntityName());
292 | $manyToManyField = $query->getField($manyToMany->getName());
293 | static::assertObject([
294 | 'association' => ObjectType::class,
295 | ], $manyToManyField->getType());
296 | static::assertConnectionObject([
297 | 'manyToMany' => ObjectType::class,
298 | 'manyToOneId' => IDType::class,
299 | 'manyToOne' => ObjectType::class
300 | ], $manyToManyField->getType()->getField('association')->getType());
301 | static::assertInputArgs([
302 | 'id' => NonNull::class,
303 | 'association' => ListOfType::class,
304 | ], $manyToManyField);
305 |
306 | $delete = new Mutation(Mutation::ACTION_DELETE, ManyToManyEntity::getEntityName());
307 | $deleteField = $query->getField($delete->getName());
308 | static::assertInstanceOf(IDType::class, $deleteField->getType());
309 | static::assertCount(1, $deleteField->args);
310 | static::assertInstanceOf(NonNull::class, $deleteField->getArg('id')->getType());
311 |
312 | $manyToOne = new Mutation(Mutation::ACTION_CREATE, ManyToOneEntity::getEntityName());
313 | $manyToOneField = $query->getField($manyToOne->getName());
314 | static::assertObject([
315 | 'association' => ObjectType::class,
316 | ], $manyToOneField->getType());
317 | static::assertConnectionObject([
318 | 'manyToMany' => ObjectType::class,
319 | 'manyToOneId' => IDType::class,
320 | 'manyToOne' => ObjectType::class
321 | ], $manyToOneField->getType()->getField('association')->getType());
322 | static::assertInputArgs([
323 | 'id' => IDType::class,
324 | 'association' => ListOfType::class,
325 | ], $manyToOneField);
326 |
327 | $manyToOne = new Mutation(Mutation::ACTION_UPDATE, ManyToOneEntity::getEntityName());
328 | $manyToOneField = $query->getField($manyToOne->getName());
329 | static::assertObject([
330 | 'association' => ObjectType::class,
331 | ], $manyToOneField->getType());
332 | static::assertConnectionObject([
333 | 'manyToMany' => ObjectType::class,
334 | 'manyToOneId' => IDType::class,
335 | 'manyToOne' => ObjectType::class
336 | ], $manyToOneField->getType()->getField('association')->getType());
337 | static::assertInputArgs([
338 | 'id' => NonNull::class,
339 | 'association' => ListOfType::class,
340 | ], $manyToOneField);
341 |
342 | $delete = new Mutation(Mutation::ACTION_DELETE, ManyToOneEntity::getEntityName());
343 | $deleteField = $query->getField($delete->getName());
344 | static::assertInstanceOf(IDType::class, $deleteField->getType());
345 | static::assertCount(1, $deleteField->args);
346 | static::assertInstanceOf(NonNull::class, $deleteField->getArg('id')->getType());
347 | }
348 |
349 | public function testGetMutationIgnoresTranslationEntity()
350 | {
351 | $this->definitionRegistry->expects($this->once())
352 | ->method('getDefinitions')
353 | ->willReturn([ProductTranslationDefinition::class]);
354 |
355 | $query = $this->typeRegistry->getMutation();
356 | static::assertInstanceOf(ObjectType::class, $query);
357 | static::assertEquals('Mutation', $query->name);
358 | static::assertCount(0, $query->getFields());
359 | }
360 |
361 | public function testGetMutationIgnoresMappingEntity()
362 | {
363 | $this->definitionRegistry->expects($this->once())
364 | ->method('getDefinitions')
365 | ->willReturn([ProductCategoryDefinition::class]);
366 |
367 | $query = $this->typeRegistry->getMutation();
368 | static::assertInstanceOf(ObjectType::class, $query);
369 | static::assertEquals('Mutation', $query->name);
370 | static::assertCount(0, $query->getFields());
371 | }
372 |
373 | public function testGetMutationWithDefault()
374 | {
375 | $this->definitionRegistry->expects($this->once())
376 | ->method('getDefinitions')
377 | ->willReturn([BaseEntityWithDefaults::class]);
378 |
379 | $query = $this->typeRegistry->getMutation();
380 | static::assertInstanceOf(ObjectType::class, $query);
381 | static::assertEquals('Mutation', $query->name);
382 | static::assertCount(3, $query->getFields());
383 |
384 | $create = new Mutation(Mutation::ACTION_CREATE, BaseEntityWithDefaults::getEntityName());
385 | $baseField = $query->getField($create->getName());
386 | static::assertObject([
387 | 'id' => NonNull::class,
388 | 'string' => StringType::class
389 | ], $baseField->getType());
390 |
391 | static::assertInputArgs([
392 | 'id' => IDType::class,
393 | 'string' => StringType::class
394 | ], $baseField);
395 |
396 | static::assertDefault(
397 | 'test',
398 | $baseField->getArg('string')
399 | );
400 |
401 | $update = new Mutation(Mutation::ACTION_UPDATE, BaseEntityWithDefaults::getEntityName());
402 | $baseField = $query->getField($update->getName());
403 | static::assertObject([
404 | 'id' => NonNull::class,
405 | 'string' => StringType::class
406 | ], $baseField->getType());
407 |
408 | static::assertInputArgs([
409 | 'id' => NonNull::class,
410 | 'string' => StringType::class
411 | ], $baseField);
412 |
413 | static::assertFalse($baseField->getArg('string')->defaultValueExists());
414 |
415 | $delete = new Mutation(Mutation::ACTION_DELETE, BaseEntityWithDefaults::getEntityName());
416 | $deleteField = $query->getField($delete->getName());
417 | static::assertInstanceOf(IDType::class, $deleteField->getType());
418 | static::assertCount(1, $deleteField->args);
419 | static::assertInstanceOf(NonNull::class, $deleteField->getArg('id')->getType());
420 | }
421 | }
--------------------------------------------------------------------------------
/Test/TestKernel.php:
--------------------------------------------------------------------------------
1 | add(new SwagGraphQL(true));
13 | }
14 |
15 | public function getProjectDir(): string
16 | {
17 | return parent::getProjectDir() . '/../../..';
18 | }
19 | }
--------------------------------------------------------------------------------
/Test/Traits/GraphqlApiTest.php:
--------------------------------------------------------------------------------
1 | $query])
22 | );
23 | $request->headers->add(['content_type' => 'application/json']);
24 |
25 | return $request;
26 | }
27 | }
--------------------------------------------------------------------------------
/Test/Traits/SchemaTestTrait.php:
--------------------------------------------------------------------------------
1 | $type) {
29 | static::assertInstanceOf($type, $object->getField($field)->getType());
30 | }
31 | }
32 |
33 | private function assertInputArgs(array $expectedFields, FieldDefinition $object): void
34 | {
35 | foreach ($expectedFields as $field => $type) {
36 | static::assertInstanceOf($type, $object->getArg($field)->getType());
37 | }
38 | }
39 |
40 | private function assertConnectionObject(array $expectedFields, ObjectType $object): void
41 | {
42 | static::assertInstanceOf(IntType::class, $object->getField('total')->getType());
43 | static::assertInstanceOf(ObjectType::class, $object->getField('pageInfo')->getType());
44 | $this->assertPageInfo($object->getField('pageInfo')->getType());
45 |
46 | static::assertInstanceOf(ListOfType::class, $object->getField('aggregations')->getType());
47 | static::assertInstanceOf(ObjectType::class, $object->getField('aggregations')->getType()->getWrappedType());
48 | $this->assertAggregations($object->getField('aggregations')->getType()->getWrappedType());
49 |
50 | static::assertInstanceOf(ListOfType::class, $object->getField('edges')->getType());
51 | static::assertInstanceOf(ObjectType::class, $object->getField('edges')->getType()->getWrappedType());
52 | $this->assertEdges($expectedFields, $object->getField('edges')->getType()->getWrappedType());
53 | }
54 |
55 | private function assertPageInfo(ObjectType $object): void
56 | {
57 | static::assertEquals('PageInfo', $object->name);
58 | static::assertInstanceOf(IDType::class, $object->getField('endCursor')->getType());
59 | static::assertInstanceOf(BooleanType::class, $object->getField('hasNextPage')->getType());
60 | }
61 |
62 | private function assertAggregations(ObjectType $object): void
63 | {
64 | static::assertEquals('AggregationResults', $object->name);
65 | static::assertInstanceOf(StringType::class, $object->getField('name')->getType());
66 | static::assertInstanceOf(ListOfType::class, $object->getField('buckets')->getType());
67 | static::assertInstanceOf(ObjectType::class, $object->getField('buckets')->getType()->getWrappedType());
68 | $this->assertAggregationBucket($object->getField('buckets')->getType()->getWrappedType());
69 | }
70 |
71 | private function assertAggregationBucket(ObjectType $object): void
72 | {
73 | static::assertEquals('AggregationBucket', $object->name);
74 | static::assertInstanceOf(ListOfType::class, $object->getField('results')->getType());
75 | static::assertInstanceOf(ObjectType::class, $object->getField('results')->getType()->getWrappedType());
76 | $this->assertAggregationResult($object->getField('results')->getType()->getWrappedType());
77 |
78 | static::assertInstanceOf(ListOfType::class, $object->getField('keys')->getType());
79 | static::assertInstanceOf(ObjectType::class, $object->getField('keys')->getType()->getWrappedType());
80 | $this->assertAggregationKey($object->getField('keys')->getType()->getWrappedType());
81 | }
82 |
83 | private function assertAggregationResult(ObjectType $object): void
84 | {
85 | static::assertEquals('AggregationResult', $object->name);
86 | static::assertInstanceOf(StringType::class, $object->getField('type')->getType());
87 | static::assertInstanceOf(StringType::class, $object->getField('result')->getType());
88 | }
89 |
90 | private function assertAggregationKey(ObjectType $object): void
91 | {
92 | static::assertEquals('AggregationKey', $object->name);
93 | static::assertInstanceOf(StringType::class, $object->getField('field')->getType());
94 | static::assertInstanceOf(StringType::class, $object->getField('value')->getType());
95 | }
96 |
97 | private function assertEdges(array $expectedFields, ObjectType $object): void
98 | {
99 | static::assertInstanceOf(IDType::class, $object->getField('cursor')->getType());
100 | static::assertInstanceOf(ObjectType::class, $object->getField('node')->getType());
101 | $this->assertObject($expectedFields, $object->getField('node')->getType());
102 | }
103 |
104 | private function assertConnectionArgs(FieldDefinition $field): void
105 | {
106 | static::assertInstanceOf(IntType::class, $field->getArg('first')->getType());
107 | static::assertInstanceOf(StringType::class, $field->getArg('after')->getType());
108 |
109 | static::assertInstanceOf(StringType::class, $field->getArg('sortBy')->getType());
110 | $this->assertEnum([FieldSorting::ASCENDING, FieldSorting::DESCENDING], $field->getArg('sortDirection')->getType());
111 |
112 | static::assertInstanceOf(InputObjectType::class, $field->getArg('query')->getType());
113 | $this->assertQueryType($field->getArg('query')->getType());
114 |
115 | static::assertInstanceOf(ListOfType::class, $field->getArg('aggregations')->getType());
116 | static::assertInstanceOf(InputObjectType::class, $field->getArg('aggregations')->getType()->getWrappedType());
117 | $this->assertAggregation($field->getArg('aggregations')->getType()->getWrappedType());
118 | }
119 |
120 | private function assertQueryType(InputObjectType $object): void
121 | {
122 | static::assertEquals('SearchQuery', $object->name);
123 | static::assertInstanceOf(StringType::class, $object->getField('field')->getType());
124 | static::assertInstanceOf(StringType::class, $object->getField('value')->getType());
125 |
126 | static::assertInstanceOf(NonNull::class, $object->getField('type')->getType());
127 | $this->assertEnum(['equals', 'equalsAny', 'contains', 'multi', 'not', 'range'], $object->getField('type')->getType()->getWrappedType());
128 |
129 | $this->assertEnum([MultiFilter::CONNECTION_AND, MultiFilter::CONNECTION_OR], $object->getField('operator')->getType());
130 |
131 | static::assertInstanceOf(ListOfType::class, $object->getField('queries')->getType());
132 | static::assertInstanceOf(InputObjectType::class, $object->getField('queries')->getType()->getWrappedType());
133 | static::assertEquals('SearchQuery', $object->getField('queries')->getType()->getWrappedType()->name);
134 |
135 | static::assertInstanceOf(ListOfType::class, $object->getField('parameters')->getType());
136 | static::assertInstanceOf(InputObjectType::class, $object->getField('parameters')->getType()->getWrappedType());
137 | $this->assertQueryParameters($object->getField('parameters')->getType()->getWrappedType());
138 | }
139 |
140 | private function assertQueryParameters(InputObjectType $object): void
141 | {
142 | static::assertEquals('Parameter', $object->name);
143 | static::assertInstanceOf(NonNull::class, $object->getField('operator')->getType());
144 | static::assertInstanceOf(FloatType::class, $object->getField('value')->getType()->getWrappedType());
145 | static::assertInstanceOf(NonNull::class, $object->getField('operator')->getType());
146 | $this->assertEnum(['GT', 'GTE', 'LT', 'LTE'], $object->getField('operator')->getType()->getWrappedType());
147 | }
148 |
149 | private function assertAggregation(InputObjectType $object): void
150 | {
151 | static::assertEquals('Aggregation', $object->name);
152 | static::assertInstanceOf(NonNull::class, $object->getField('name')->getType());
153 | static::assertInstanceOf(StringType::class, $object->getField('name')->getType()->getWrappedType());
154 | static::assertInstanceOf(NonNull::class, $object->getField('field')->getType());
155 | static::assertInstanceOf(StringType::class, $object->getField('field')->getType()->getWrappedType());
156 | static::assertInstanceOf(NonNull::class, $object->getField('type')->getType());
157 | $this->assertEnum(['avg', 'cardinality', 'count', 'max', 'min', 'stats', 'sum', 'value_count'], $object->getField('type')->getType()->getWrappedType());
158 | }
159 |
160 | private function assertEnum(array $expectedValues, EnumType $enum): void
161 | {
162 | static::assertCount(count($expectedValues), $enum->getValues());
163 |
164 | foreach ($expectedValues as $value) {
165 | static::assertInstanceOf(EnumValueDefinition::class, $enum->getValue($value));
166 | }
167 | }
168 |
169 | private function assertDefault($default, FieldArgument $field)
170 | {
171 | static::assertTrue($field->defaultValueExists());
172 | static::assertEquals($default, $field->defaultValue);
173 | }
174 | }
--------------------------------------------------------------------------------
/Test/_fixtures/AssociationEntity.php:
--------------------------------------------------------------------------------
1 | setFlags(new PrimaryKey(), new Required()),
27 | new ManyToManyAssociationField(
28 | 'manyToMany',
29 | ManyToManyEntity::class,
30 | MappingEntity::class,
31 | 'association_id',
32 | 'many_to_many_id'
33 | ),
34 | new FkField(
35 | 'many_to_one_id',
36 | 'manyToOneId',
37 | ManyToOneEntity::class
38 | ),
39 | new ReferenceVersionField(ManyToOneEntity::class),
40 | new ManyToOneAssociationField(
41 | 'manyToOne',
42 | 'many_to_one',
43 | ManyToOneEntity::class
44 | )
45 | ]);
46 | }
47 | }
--------------------------------------------------------------------------------
/Test/_fixtures/BaseEntity.php:
--------------------------------------------------------------------------------
1 | setFlags(new PrimaryKey(), new Required()),
29 | new BoolField('bool', 'bool'),
30 | new DateField('date', 'date'),
31 | new IntField('int', 'int'),
32 | new FloatField('float', 'float'),
33 | new JsonField('json', 'json'),
34 | new StringField('string', 'string')
35 | ]);
36 | }
37 | }
--------------------------------------------------------------------------------
/Test/_fixtures/BaseEntityWithDefaults.php:
--------------------------------------------------------------------------------
1 | setFlags(new PrimaryKey(), new Required()),
25 | new StringField('string', 'string')
26 | ]);
27 | }
28 |
29 | public static function getDefaults(EntityExistence $existence): array
30 | {
31 | if ($existence->exists()) {
32 | return [];
33 | }
34 |
35 | return [
36 | 'string' => 'test'
37 | ];
38 | }
39 | }
--------------------------------------------------------------------------------
/Test/_fixtures/ManyToManyEntity.php:
--------------------------------------------------------------------------------
1 | setFlags(new PrimaryKey(), new Required()),
24 | new ManyToManyAssociationField(
25 | 'association',
26 | AssociationEntity::class,
27 | MappingEntity::class,
28 | 'many_to_many_id',
29 | 'association_id'
30 | )
31 | ]);
32 | }
33 | }
--------------------------------------------------------------------------------
/Test/_fixtures/ManyToOneEntity.php:
--------------------------------------------------------------------------------
1 | setFlags(new PrimaryKey(), new Required()),
24 | new OneToManyAssociationField(
25 | 'association',
26 | AssociationEntity::class,
27 | 'many_to_one_id'
28 | )
29 | ]);
30 | }
31 | }
--------------------------------------------------------------------------------
/Test/_fixtures/MappingEntity.php:
--------------------------------------------------------------------------------
1 | setFlags(new PrimaryKey(), new Required()),
24 |
25 | (new FkField('many_to_many_id', 'manyToManyId', ManyToManyEntity::class))->setFlags(new PrimaryKey(), new Required()),
26 |
27 | new ManyToOneAssociationField('association', 'association_id', AssociationEntity::class),
28 | new ManyToOneAssociationField('manyToMany', 'many_to_many_id', ManyToManyEntity::class),
29 | ]);
30 | }
31 | }
--------------------------------------------------------------------------------
/TestBootstrap.php:
--------------------------------------------------------------------------------
1 | getParentClass();
7 | $dir = $rootDir = \dirname($r->getFileName());
8 | while (!file_exists($dir . '/composer.json')) {
9 | if ($dir === \dirname($dir)) {
10 | return $rootDir;
11 | }
12 | $dir = \dirname($dir);
13 | }
14 |
15 | return $dir;
16 | }
17 |
18 | $projectDir = getProjectDir();
19 |
20 | require_once $projectDir . '/vendor/autoload.php';
21 |
22 | use Symfony\Component\Dotenv\Dotenv;
23 |
24 | if (!class_exists(Dotenv::class)) {
25 | throw new \RuntimeException('APP_ENV environment variable is not defined. You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.');
26 | }
27 | (new Dotenv())->load($projectDir . '/.env');
28 |
29 | putenv('DATABASE_URL=' . getenv('DATABASE_URL') . '_test');
30 |
--------------------------------------------------------------------------------
/Types/DateType.php:
--------------------------------------------------------------------------------
1 | format(DATE_ATOM);
32 | }
33 |
34 | /**
35 | * Parses an externally provided value (query variable) to use as an input
36 | *
37 | * In the case of an invalid value this method must throw an Exception
38 | *
39 | * @param mixed $value
40 | * @return mixed
41 | * @throws Error
42 | */
43 | public function parseValue($value)
44 | {
45 | $date = \DateTime::createFromFormat(DATE_ATOM, $value);
46 | if ($date === false) {
47 | throw new Error("Could not parse following Date: " . $value);
48 | }
49 |
50 | return $date;
51 | }
52 |
53 | /**
54 | * Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input
55 | *
56 | * In the case of an invalid node or value this method must throw an Exception
57 | *
58 | * @param Node $valueNode
59 | * @param mixed[]|null $variables
60 | * @return mixed
61 | * @throws \Exception
62 | */
63 | public function parseLiteral($valueNode, ?array $variables = null)
64 | {
65 | if (!$valueNode instanceof StringValueNode) {
66 | $kind = 'undefined';
67 | if (property_exists($valueNode, 'kind')) {
68 | $kind = $valueNode->kind;
69 | }
70 |
71 | throw new Error('Query error: Can only parse strings got: ' . $kind, $valueNode);
72 | }
73 | $date = \DateTime::createFromFormat(DATE_ATOM, $valueNode->value);
74 | if ($date === false) {
75 | throw new Error("Could not parse following Date: " . $valueNode->value);
76 | }
77 |
78 | return $date;
79 | }
80 | }
--------------------------------------------------------------------------------
/Types/JsonType.php:
--------------------------------------------------------------------------------
1 | jsonSerialize();
26 | }
27 |
28 | return $value;
29 | }
30 |
31 | /**
32 | * Parses an externally provided value (query variable) to use as an input
33 | *
34 | * In the case of an invalid value this method must throw an Exception
35 | *
36 | * @param mixed $value
37 | * @return mixed
38 | * @throws Error
39 | */
40 | public function parseValue($value)
41 | {
42 | if (is_array($value)) {
43 | return $value;
44 | }
45 |
46 | return json_decode($value, true);
47 | }
48 |
49 | /**
50 | * Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input
51 | *
52 | * In the case of an invalid node or value this method must throw an Exception
53 | *
54 | * @param Node $valueNode
55 | * @param mixed[]|null $variables
56 | * @return mixed
57 | * @throws \Exception
58 | */
59 | public function parseLiteral($valueNode, ?array $variables = null)
60 | {
61 | if (!$valueNode instanceof StringValueNode) {
62 | $kind = 'undefined';
63 | if (property_exists($valueNode, 'kind')) {
64 | $kind = $valueNode->kind;
65 | }
66 |
67 | throw new Error('Query error: Can only parse strings got: ' . $kind, $valueNode);
68 | }
69 |
70 | return json_decode($valueNode->value, true);
71 | }
72 | }
--------------------------------------------------------------------------------
/bin/phpstan.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ./../../../vendor/shopware/platform/bin/phpstan.phar analyze --level 5 --configuration phpstan.neon --autoload-file=../../../vendor/autoload.php .
--------------------------------------------------------------------------------
/bin/phpunit-coverage.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ./../../../vendor/bin/phpunit --coverage-html coverage
--------------------------------------------------------------------------------
/bin/phpunit.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ./../../../vendor/bin/phpunit
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shopware/swag-graphql",
3 | "type": "shopware-platform-plugin",
4 | "minimum-stability": "dev",
5 | "description": "A Simple Plugin that exposes an GraphQL-API for the Shopware 6 Core-API.",
6 | "keywords": ["shopware", "shop"],
7 | "homepage": "http://www.shopware.com",
8 | "license": "MIT",
9 | "authors": [
10 | {
11 | "name": "shopware AG"
12 | }
13 | ],
14 | "require": {
15 | "webonyx/graphql-php": "dev-master",
16 | "doctrine/inflector": "1.3.0"
17 | },
18 | "extra": {
19 | "shopware-plugin-class": "SwagGraphQL\\SwagGraphQL",
20 | "copyright": "(c) by shopware AG",
21 | "label": {
22 | "de_DE": "GraphQL",
23 | "en_GB": "GraphQL"
24 | }
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "SwagGraphQL\\": ""
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | reportUnmatchedIgnoredErrors: false
3 | excludes_analyse:
4 | - %currentWorkingDirectory%/Test/*
5 | - %currentWorkingDirectory%/vendor/*
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Test
23 |
24 |
25 |
26 |
27 |
28 | ./
29 |
30 | ./Test
31 |
32 | SwagGraphQL.php
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 0
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Attention!
2 |
3 | **This was a POC for a GraphQL API fore a very early Shopware 6.0 state. It is unmaintained and untested for newer versions since then. Handle with care!**
4 |
5 | # SwagGraphQL
6 |
7 | A simple plugin that exposes an GraphQL-API for the Shopware Platform Core-API.
8 |
9 | ## Installation
10 |
11 | Clone this repo in your `custom/plugins` folder of your Shopware Platform Template.
12 |
13 | run:
14 | ```
15 | cd custom/plugins/SwagGraphQL
16 | composer install
17 | cd ../../..
18 | bin/console plugin:install SwagGraphQL
19 | bin/console plugin:activate SwagGraphQL
20 | ```
21 |
22 | After installation the GraphQL endpoint is available under `{baseUrl}/graphql/query`.
23 |
24 | ## Getting started
25 |
26 | Getting started with [GraphQL](https://graphql.org/learn/).
27 |
28 | The easiest way to fiddle around with the Shopware GraphQL-API is to use
29 | [GraphiQL](https://github.com/graphql/graphiql), for example as a [Chrome-Extension](https://chrome.google.com/webstore/detail/chromeiql)
30 |
31 | ## Custom Fields
32 |
33 | You can define your custom fields, by implementing the `GraphQLField` Interface and tagging your Field
34 | either with the `swag_graphql.queries` or `swag_graphql.mutations` tag.
35 | In either case you have to specify the name under which the field will be queryable inside the service tag,
36 | either as `mutation` or `query`
37 |
38 | ####Example:
39 |
40 | in `services.xml`:
41 | ```xml
42 |
43 |
44 |
45 | ```
46 | your class:
47 | ```php
48 | class GenerateUserKeyAction implements GraphQLField
49 | {
50 | public function returnType(): Type
51 | {
52 | return new ObjectType([
53 | 'name' => 'UserAccessKey',
54 | 'fields' => [
55 | 'accessKey' => [
56 | 'type' => Type::nonNull(Type::id())
57 | ],
58 | 'secretAccessKey' => [
59 | 'type' => Type::nonNull(Type::id())
60 | ]
61 | ]
62 | ]);
63 | }
64 |
65 | public function defineArgs(): array
66 | {
67 | return [];
68 | }
69 |
70 | public function description(): string
71 | {
72 | return 'Generates the access keys for a user.';
73 | }
74 |
75 | public function resolve($rootValue, $args, Context $context, ResolveInfo $info)
76 | {
77 | return [
78 | 'accessKey' => AccessKeyHelper::generateAccessKey('user'),
79 | 'secretAccessKey' => AccessKeyHelper::generateSecretAccessKey(),
80 | ];
81 | }
82 | }
83 | ```
84 |
85 | ## Dependencies
86 |
87 | It uses [webonyx/graphql-php](https://github.com/webonyx/graphql-php) for the GraphQL part
88 | and the Shopware 6 Framework-Bundle for schema generation and query resolving.
89 |
90 | The Tests also depend on the Shopware 6 Content-Bundle.
91 |
92 | ## Known Problems
93 |
94 | Nested connections don't really work. The connection information (total, pageInfo and aggregation) aren't returned.
95 |
--------------------------------------------------------------------------------