├── .gitignore ├── ConfigDrivers ├── FileDriver.php └── Yaml │ └── YamlDriver.php ├── DependencyInjection ├── Configuration.php └── GraphQLExtension.php ├── Error └── ValidationError.php ├── Exception └── QueryException.php ├── GraphQL.php ├── GraphQLBundle.php ├── LICENSE ├── README.md ├── Resources └── config │ └── services.yml ├── Support ├── AbstractSupport.php ├── Field.php ├── InterfaceType.php ├── Mutation.php ├── Query.php └── Type.php └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | /.idea/ 4 | composer.lock 5 | -------------------------------------------------------------------------------- /ConfigDrivers/FileDriver.php: -------------------------------------------------------------------------------- 1 | path = $file; 17 | $this->load(); 18 | } 19 | 20 | /** 21 | * @param string $path 22 | * @return string 23 | * @throws \Exception 24 | */ 25 | protected function getFileContent($path) 26 | { 27 | if (!is_file($path)) { 28 | throw new \Exception(sprintf('Config file "%s" not found', $path)); 29 | } 30 | 31 | return file_get_contents($path); 32 | } 33 | 34 | /** 35 | * @return string 36 | */ 37 | protected function getPath() 38 | { 39 | return $this->path; 40 | } 41 | 42 | /** 43 | * @return array 44 | */ 45 | public function getTypes() 46 | { 47 | return $this->types; 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public function getSchema() 54 | { 55 | return $this->schema; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ConfigDrivers/Yaml/YamlDriver.php: -------------------------------------------------------------------------------- 1 | getFileContent($this->getPath())); 12 | 13 | foreach ($configMapping as $type => $value) { 14 | switch ($type) { 15 | case 'types': 16 | $this->types = $value; 17 | break; 18 | case 'schema': 19 | $this->schema = $value; 20 | break; 21 | default: 22 | throw new \UnexpectedValueException(sprintf('Unsupported key "%s"')); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('suribit_graph_ql_core'); 22 | 23 | // Here you should define the parameters that are allowed to 24 | // configure your bundle. See the documentation linked above for 25 | // more information on that topic. 26 | 27 | return $treeBuilder; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DependencyInjection/GraphQLExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 24 | 25 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 26 | $loader->load('services.yml'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Error/ValidationError.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 15 | 16 | return $this; 17 | } 18 | 19 | public function getValidatorMessages() 20 | { 21 | return $this->validator ? $this->validator->messages():[]; 22 | } 23 | } -------------------------------------------------------------------------------- /Exception/QueryException.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 23 | } 24 | 25 | /** 26 | * @return FormattedError[] 27 | */ 28 | public function getErrors() 29 | { 30 | return $this->errors; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /GraphQL.php: -------------------------------------------------------------------------------- 1 | em = $em; 29 | $this->config = $config; 30 | } 31 | 32 | /** 33 | * @return Schema 34 | */ 35 | public function schema() 36 | { 37 | $this->typesInstances = []; 38 | 39 | $schema = $this->config->getSchema(); 40 | $types = $this->config->getTypes(); 41 | 42 | foreach($types as $name => $type) 43 | { 44 | $this->addType($type, $name); 45 | } 46 | 47 | foreach($types as $name => $type) 48 | { 49 | $this->type($name); 50 | } 51 | 52 | $configQuery = $schema['query']; 53 | $configMutation = $schema['mutation']; 54 | 55 | $queryFields = array_merge($configQuery, $this->queries); 56 | /** @var ObjectType $queryType */ 57 | $queryType = $this->buildTypeFromFields($queryFields, [ 58 | 'name' => 'Query' 59 | ]); 60 | 61 | $mutationFields = array_merge($configMutation, $this->mutations); 62 | /** @var ObjectType $queryType */ 63 | $mutationType = $this->buildTypeFromFields($mutationFields, [ 64 | 'name' => 'Mutation' 65 | ]); 66 | 67 | return new Schema($queryType, $mutationType); 68 | } 69 | 70 | /** 71 | * @param array $fields 72 | * @param array $opts 73 | * @return ObjectType 74 | */ 75 | protected function buildTypeFromFields($fields, $opts = []) 76 | { 77 | if (empty($fields)) { 78 | return null; 79 | } 80 | 81 | $typeFields = []; 82 | foreach($fields as $key => $field) 83 | { 84 | if(is_string($field)) 85 | { 86 | $fieldObj = new $field(); 87 | $fieldObj->setManager($this); 88 | $typeFields[$key] = $fieldObj->toArray(); 89 | } 90 | else 91 | { 92 | $typeFields[$key] = $field; 93 | } 94 | } 95 | 96 | return new ObjectType(array_merge([ 97 | 'fields' => $typeFields 98 | ], $opts)); 99 | } 100 | 101 | /** 102 | * @param string $query 103 | * @param array $params 104 | * @return array 105 | */ 106 | public function query($query, $params = []) 107 | { 108 | $executionResult = $this->queryAndReturnResult($query, $params); 109 | 110 | if (!empty($executionResult->errors)) 111 | { 112 | return [ 113 | 'data' => $executionResult->data, 114 | 'errors' => array_map([$this, 'formatError'], $executionResult->errors) 115 | ]; 116 | } 117 | else 118 | { 119 | return [ 120 | 'data' => $executionResult->data 121 | ]; 122 | } 123 | } 124 | 125 | public function queryAndReturnResult($query, $params = []) 126 | { 127 | $schema = $this->schema(); 128 | $result = GraphQLBase::executeAndReturnResult($schema, $query, null, $params); 129 | return $result; 130 | } 131 | 132 | public function addMutation($name, $mutator) 133 | { 134 | $this->mutations[$name] = $mutator; 135 | } 136 | 137 | public function addQuery($name, $query) 138 | { 139 | $this->queries[$name] = $query; 140 | } 141 | 142 | public function addType($class, $name) 143 | { 144 | $this->types[$name] = $class; 145 | } 146 | 147 | public function type($name, $fresh = false) 148 | { 149 | if(!isset($this->types[$name])) 150 | { 151 | throw new \Exception('Type '.$name.' not found.'); 152 | } 153 | 154 | if(!$fresh && isset($this->typesInstances[$name])) 155 | { 156 | return $this->typesInstances[$name]; 157 | } 158 | 159 | /** @var Type $type */ 160 | $type = $this->types[$name]; 161 | $type = new $type(); 162 | $type->setManager($this); 163 | $type->toType(); 164 | 165 | $this->typesInstances[$name] = $type; 166 | 167 | //Check if the object has interfaces 168 | if($type->interfaces) 169 | { 170 | InterfaceType::addImplementationToInterfaces($type->original); 171 | } 172 | 173 | return $type; 174 | } 175 | 176 | public function formatError(Error $e) 177 | { 178 | $error = [ 179 | 'message' => $e->getMessage() 180 | ]; 181 | 182 | $locations = $e->getLocations(); 183 | if(!empty($locations)) 184 | { 185 | $error['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations); 186 | } 187 | 188 | $previous = $e->getPrevious(); 189 | if($previous && $previous instanceof ValidationError) 190 | { 191 | $error['validation'] = $previous->getValidatorMessages(); 192 | } 193 | 194 | return $error; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /GraphQLBundle.php: -------------------------------------------------------------------------------- 1 | 'Country', 64 | 'description' => 'A Country' 65 | ]; 66 | 67 | public function fields() 68 | { 69 | return [ 70 | 'id' => [ 71 | 'type' => TypeBase::nonNull(TypeBase::int()), 72 | 'description' => 'The id of the country' 73 | ], 74 | 'name' => [ 75 | 'type' => TypeBase::string(), 76 | 'description' => 'The name of country' 77 | ], 78 | 'status' => [ 79 | 'type' => TypeBase::int(), 80 | 'description' => 'The status of country' 81 | ] 82 | ]; 83 | } 84 | } 85 | 86 | ``` 87 | 88 | **5-** Create Query `src/path your bundle/Queries/Country.php` 89 | ```php 90 | 'Country query' 101 | ]; 102 | 103 | public function type() 104 | { 105 | return $this->manager->type('country'); 106 | } 107 | 108 | public function args() 109 | { 110 | return [ 111 | 'id' => ['name' => 'id', 'type' => Type::int()], 112 | ]; 113 | } 114 | 115 | public function resolve($root, $args) 116 | { 117 | $em = $this->manager->em; // Doctrine Entity Manager 118 | return [ 119 | 'id' => `, 120 | 'name' => 'Russia', 121 | 'status' => 1 122 | ]; 123 | } 124 | } 125 | ``` 126 | 127 | **6-** Create config for graphql schema `src/path your bundle/Resources/config/graphql.yml` 128 | ```yaml 129 | types: 130 | country: 'Lgck\GraphQlBundle\Types\Country' 131 | 132 | schema: 133 | query: 134 | country: 'Lgck\GraphQlBundle\Queries\Country' 135 | 136 | mutation: [] 137 | ``` 138 | 139 | **7-** Edit the file `src/path your bundle/Resources/config/services.yml` 140 | ```yaml 141 | services: 142 | lgck_graph_ql.mapping.driver.yaml: 143 | public: true 144 | class: Suribit\GraphQLBundle\ConfigDrivers\Yaml\YamlDriver 145 | arguments: 146 | - "%kernel.root_dir%/../src/path your bundle/Resources/config/graphql.yml" 147 | 148 | lgck_graph_ql.manager: 149 | class: Suribit\GraphQLBundle\GraphQL 150 | arguments: 151 | - @doctrine.orm.entity_manager 152 | - @lgck_graph_ql.mapping.driver.yaml 153 | ``` 154 | 155 | **8-** Create a controller that will be the starting point for processing the request 156 | ```php 157 | get('lgck_graph_ql.manager'); 166 | $query = $request->request->get('query'); 167 | try { 168 | $data = $manager->query($query); 169 | } catch (QueryException $e) { 170 | $response = new JsonResponse($e->getErrors(), 500); 171 | return $response; 172 | } 173 | 174 | $response = new JsonResponse($data); 175 | return $response; 176 | } 177 | } 178 | ``` 179 | 180 | **9-** Now it is possible to send a data request 181 | ```graphql 182 | query FooBar { 183 | country(id: 1) { 184 | id, 185 | name, 186 | status 187 | } 188 | } 189 | ``` 190 | 191 | TODO: 192 | 1. Add the complete documentation 193 | 2. Add validation -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g1ibby/GraphQLBundle/f8e540155bd809386da76455e90a644223078c37/Resources/config/services.yml -------------------------------------------------------------------------------- /Support/AbstractSupport.php: -------------------------------------------------------------------------------- 1 | manager = $manager; 32 | } 33 | 34 | /** 35 | * Get an attribute from the container. 36 | * 37 | * @param string $key 38 | * @param mixed $default 39 | * @return mixed 40 | */ 41 | public function get($key, $default = null) 42 | { 43 | if (array_key_exists($key, $this->attributes)) { 44 | return $this->attributes[$key]; 45 | } 46 | return value($default); 47 | } 48 | /** 49 | * Get the attributes from the container. 50 | * 51 | * @return array 52 | */ 53 | public function getAttributes() 54 | { 55 | return $this->attributes; 56 | } 57 | /** 58 | * Return all attributes. 59 | * 60 | * @return array 61 | */ 62 | public function toArray() 63 | { 64 | return $this->attributes; 65 | } 66 | /** 67 | * Convert the object into something JSON serializable. 68 | * 69 | * @return array 70 | */ 71 | public function jsonSerialize() 72 | { 73 | return $this->toArray(); 74 | } 75 | /** 76 | * Convert to JSON. 77 | * 78 | * @param int $options 79 | * @return string 80 | */ 81 | public function toJson($options = 0) 82 | { 83 | return json_encode($this->jsonSerialize(), $options); 84 | } 85 | /** 86 | * Determine if the given offset exists. 87 | * 88 | * @param string $offset 89 | * @return bool 90 | */ 91 | public function offsetExists($offset) 92 | { 93 | return isset($this->{$offset}); 94 | } 95 | /** 96 | * Get the value for a given offset. 97 | * 98 | * @param string $offset 99 | * @return mixed 100 | */ 101 | public function offsetGet($offset) 102 | { 103 | return $this->{$offset}; 104 | } 105 | /** 106 | * Set the value at the given offset. 107 | * 108 | * @param string $offset 109 | * @param mixed $value 110 | * @return void 111 | */ 112 | public function offsetSet($offset, $value) 113 | { 114 | $this->{$offset} = $value; 115 | } 116 | /** 117 | * Unset the value at the given offset. 118 | * 119 | * @param string $offset 120 | * @return void 121 | */ 122 | public function offsetUnset($offset) 123 | { 124 | unset($this->{$offset}); 125 | } 126 | /** 127 | * Handle dynamic calls to the container to set attributes. 128 | * 129 | * @param string $method 130 | * @param array $parameters 131 | * @return $this 132 | */ 133 | public function __call($method, $parameters) 134 | { 135 | $this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true; 136 | return $this; 137 | } 138 | /** 139 | * Dynamically retrieve the value of an attribute. 140 | * 141 | * @param string $key 142 | * @return mixed 143 | */ 144 | public function __get($key) 145 | { 146 | return $this->get($key); 147 | } 148 | /** 149 | * Dynamically set the value of an attribute. 150 | * 151 | * @param string $key 152 | * @param mixed $value 153 | * @return void 154 | */ 155 | public function __set($key, $value) 156 | { 157 | $this->attributes[$key] = $value; 158 | } 159 | /** 160 | * Dynamically check if an attribute is set. 161 | * 162 | * @param string $key 163 | * @return bool 164 | */ 165 | public function __isset($key) 166 | { 167 | return isset($this->attributes[$key]); 168 | } 169 | /** 170 | * Dynamically unset an attribute. 171 | * 172 | * @param string $key 173 | * @return void 174 | */ 175 | public function __unset($key) 176 | { 177 | unset($this->attributes[$key]); 178 | } 179 | 180 | protected function studly($value) 181 | { 182 | $result = ucwords(str_replace(['-', '_'], ' ', $value)); 183 | return str_replace(' ', '', $result); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Support/Field.php: -------------------------------------------------------------------------------- 1 | attributes(); 46 | $args = $this->args(); 47 | 48 | $attributes = array_merge($this->attributes, [ 49 | 'args' => $this->args() 50 | ], $attributes); 51 | 52 | $type = $this->type(); 53 | if(isset($type)) 54 | { 55 | $attributes['type'] = $type; 56 | } 57 | 58 | $resolver = $this->getResolver(); 59 | if(isset($resolver)) 60 | { 61 | $attributes['resolve'] = $resolver; 62 | } 63 | 64 | return $attributes; 65 | } 66 | 67 | /** 68 | * Convert the Fluent instance to an array. 69 | * 70 | * @return array 71 | */ 72 | public function toArray() 73 | { 74 | return $this->getAttributes(); 75 | } 76 | 77 | /** 78 | * Dynamically retrieve the value of an attribute. 79 | * 80 | * @param string $key 81 | * @return mixed 82 | */ 83 | public function __get($key) 84 | { 85 | $attributes = $this->getAttributes(); 86 | return isset($attributes[$key]) ? $attributes[$key]:null; 87 | } 88 | 89 | /** 90 | * Dynamically check if an attribute is set. 91 | * 92 | * @param string $key 93 | * @return bool 94 | */ 95 | public function __isset($key) 96 | { 97 | $attributes = $this->getAttributes(); 98 | return isset($attributes[$key]); 99 | } 100 | } -------------------------------------------------------------------------------- /Support/InterfaceType.php: -------------------------------------------------------------------------------- 1 | getTypeResolver(); 35 | if(isset($resolver)) 36 | { 37 | $attributes['resolveType'] = $resolver; 38 | } 39 | 40 | return $attributes; 41 | } 42 | 43 | public function toType() 44 | { 45 | $this->original = new BaseInterfaceType($this->toArray()); 46 | } 47 | } -------------------------------------------------------------------------------- /Support/Mutation.php: -------------------------------------------------------------------------------- 1 | args() as $name => $arg) 20 | { 21 | if(isset($arg['rules'])) 22 | { 23 | if(is_callable($arg['rules'])) 24 | { 25 | $argsRules[$name] = call_user_func_array($arg['rules'], $arguments); 26 | } 27 | else 28 | { 29 | $argsRules[$name] = $arg['rules']; 30 | } 31 | } 32 | } 33 | 34 | return array_merge($argsRules, $rules); 35 | } 36 | 37 | protected function getResolver() 38 | { 39 | if(!method_exists($this, 'resolve')) 40 | { 41 | return null; 42 | } 43 | 44 | $resolver = array($this, 'resolve'); 45 | return function() use ($resolver) 46 | { 47 | $arguments = func_get_args(); 48 | return call_user_func_array($resolver, $arguments); 49 | }; 50 | } 51 | } -------------------------------------------------------------------------------- /Support/Query.php: -------------------------------------------------------------------------------- 1 | studly($name).'Field')) 36 | { 37 | $resolver = array($this, 'resolve'.$this->studly($name).'Field'); 38 | return function() use ($resolver) 39 | { 40 | $args = func_get_args(); 41 | return call_user_func_array($resolver, $args); 42 | }; 43 | } 44 | 45 | return null; 46 | } 47 | 48 | public function getFields() 49 | { 50 | $fields = $this->fields(); 51 | $allFields = []; 52 | foreach($fields as $name => $field) 53 | { 54 | if(is_string($field)) 55 | { 56 | $field = new Field($field); 57 | $field->setManager($this); 58 | $field->name = $name; 59 | $allFields[$name] = $field->toArray(); 60 | } 61 | else 62 | { 63 | $resolver = $this->getFieldResolver($name, $field); 64 | if($resolver) 65 | { 66 | $field['resolve'] = $resolver; 67 | } 68 | $allFields[$name] = $field; 69 | } 70 | } 71 | 72 | return $allFields; 73 | } 74 | 75 | /** 76 | * Get the attributes from the container. 77 | * 78 | * @return array 79 | */ 80 | public function getAttributes() 81 | { 82 | $attributes = $this->attributes(); 83 | $fields = $this->getFields(); 84 | $interfaces = $this->interfaces(); 85 | 86 | $attributes = array_merge($this->attributes, [ 87 | 'fields' => $fields 88 | ], $attributes); 89 | 90 | if(sizeof($interfaces)) 91 | { 92 | $attributes['interfaces'] = $interfaces; 93 | } 94 | 95 | return $attributes; 96 | } 97 | 98 | /** 99 | * Convert the Fluent instance to an array. 100 | * 101 | * @return array 102 | */ 103 | public function toArray() 104 | { 105 | return $this->getAttributes(); 106 | } 107 | 108 | public function toType() 109 | { 110 | $this->original = new ObjectType($this->toArray()); 111 | } 112 | 113 | /** 114 | * Dynamically retrieve the value of an attribute. 115 | * 116 | * @param string $key 117 | * @return mixed 118 | */ 119 | public function __get($key) 120 | { 121 | $attributes = $this->getAttributes(); 122 | return isset($attributes[$key]) ? $attributes[$key]:null; 123 | } 124 | 125 | /** 126 | * Dynamically check if an attribute is set. 127 | * 128 | * @param string $key 129 | * @return bool 130 | */ 131 | public function __isset($key) 132 | { 133 | $attributes = $this->getAttributes(); 134 | return isset($attributes[$key]); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "suribit/graphql-bundle", 3 | "description": "Symfony2 GraphQl Bundle used graphql-php", 4 | "minimum-stability": "dev", 5 | "license": "MIT", 6 | "homepage": "https://github.com/suribit/GraphQLBundle", 7 | "authors": [ 8 | { 9 | "name": "Sergei Waribrus", 10 | "email": "wss.world@gmail.com" 11 | } 12 | ], 13 | "keywords": [ 14 | "graphql" 15 | ], 16 | "require": { 17 | "php": ">=5.4", 18 | "webonyx/graphql-php": "~0.5" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~4.7" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Suribit\\GraphQLBundle\\": "" 26 | } 27 | } 28 | } --------------------------------------------------------------------------------