├── .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 | --------------------------------------------------------------------------------