├── _config.yml ├── src ├── Enums │ ├── Format.php │ └── Request.php ├── Facades │ └── GraphQL.php ├── GraphqlClientServiceProvider.php └── Classes │ ├── Mutator.php │ └── Client.php ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md └── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── composer.json ├── LICENSE ├── config └── config.php └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /src/Enums/Format.php: -------------------------------------------------------------------------------- 1 | app->bind('graphqlClient', function($app) { 18 | return new Client(config('graphqlclient.graphql_endpoint')); 19 | }); 20 | 21 | $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'graphqlclient'); 22 | } 23 | 24 | /** 25 | * Bootstrap services. 26 | * 27 | * @return void 28 | */ 29 | public function boot() 30 | { 31 | if ($this->app->runningInConsole()){ 32 | $this->publishes([ 33 | __DIR__.'/../config/config.php' => config_path('graphqlclient.php'), 34 | ], 'config'); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bendeckdavid/graphql-client", 3 | "version": "1.3.2", 4 | "description": "Graphql Client for Laravel", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/bendeckdavid/graphql-laravel-client.git" 12 | }, 13 | "author": { 14 | "email": "david@bendeck.dev", 15 | "name": "David Gutierrez", 16 | "github": "BendeckDavid" 17 | }, 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/bendeckdavid/graphql-laravel-client/issues" 21 | }, 22 | "homepage": "https://github.com/bendeckdavid/graphql-laravel-client#readme", 23 | "keywords": [ 24 | "Laravel", 25 | "Graphql", 26 | "Graphql Client", 27 | "BendeckDavid" 28 | ], 29 | "autoload": { 30 | "psr-4": { 31 | "BendeckDavid\\GraphqlClient\\": "src/" 32 | } 33 | }, 34 | "extra": { 35 | "laravel":{ 36 | "providers": [ 37 | "BendeckDavid\\GraphqlClient\\GraphqlClientServiceProvider" 38 | ] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 David Gutierez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | env('GRAPHQL_AUTHENTICATION_HEADER', 'Authorization'), 13 | 'auth_credentials' => env('GRAPHQL_CREDENTIALS', null), 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | Request Authentication scheme 18 | | 19 | | Valid Schemes: basic, bearer, custom 20 | |-------------------------------------------------------------------------- 21 | | 22 | */ 23 | 24 | 'auth_scheme' => env('GRAPHQL_AUTHENTICATION', 'bearer'), 25 | 26 | 'auth_schemes' => [ 27 | 'basic' => 'Basic ', 28 | 'bearer' => 'Bearer ', 29 | 'custom' => null 30 | ], 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | GraphQL endpoint 35 | |-------------------------------------------------------------------------- 36 | | 37 | */ 38 | 39 | 'graphql_endpoint' => env('GRAPHQL_ENDPOINT', null), 40 | 41 | ]; -------------------------------------------------------------------------------- /src/Classes/Mutator.php: -------------------------------------------------------------------------------- 1 | 15 | $this->{'get'.Str::studly($key).'Attribute'}(), 16 | 17 | array_key_exists($key, $this->attributes) => 18 | $this->attributes[$key], 19 | 20 | DEFAULT => null 21 | 22 | }; 23 | } 24 | 25 | public function __call(string $method, array $arguments = []) : mixed 26 | { 27 | match(true) { 28 | 29 | // Set property, if exits, using withProperty naming 30 | Str::startsWith($method, 'with') && 31 | property_exists($this, Str::camel(substr($method, 4))) => 32 | $this->{Str::camel(substr($method, 4))} = $arguments[0], 33 | 34 | // Set attribute using withAttribute naming 35 | Str::startsWith($method, 'with') => 36 | $this->variables[Str::camel(substr($method, 4))] = $arguments[0], 37 | 38 | DEFAULT => throw new BadMethodCallException("Method [$method] does not exist.") 39 | 40 | }; 41 | 42 | return $this; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Minimal GraphQL Laravel Client 3 | 4 | Minimal GraphQL client for Laravel. 5 | 6 | 7 | ## Requirements 8 | 9 | - Composer 2+ 10 | 11 | 12 | ## Installation 13 | 14 | Install Package (Composer 2+) 15 | ```bash 16 | composer require bendeckdavid/graphql-client 17 | ``` 18 | 19 | 20 | ## Usage 21 | 22 | Enviroment variable 23 | ```php 24 | GRAPHQL_ENDPOINT="https://api.spacex.land/graphql/" 25 | ``` 26 | 27 | 28 | ## Authentication 29 | 30 | We provide a minimal authentication integration by appending the `Authorization` header to the request client. You can pass the credentials using an `env` variable. 31 | ```php 32 | GRAPHQL_CREDENTIALS="YOUR_CREDENTIALS" 33 | ``` 34 | 35 | You can also pass auth credentials at runtime using `withToken($credentials)` method. 36 | 37 | 38 | 'Authorization' header and 'Bearer' Schema are used by default. You can override the default behaviour by defining following variables in your `.env` file. 39 | ```php 40 | GRAPHQL_AUTHENTICATION_HEADER="Authorization" 41 | 42 | // Allowed: basic, bearer, custom 43 | GRAPHQL_AUTHENTICATION="bearer" 44 | ``` 45 | 46 | 47 | ## Usage/Examples 48 | 49 | Import GraphQL Client Facades 50 | ```php 51 | use BendeckDavid\GraphqlClient\Facades\GraphQL; 52 | ``` 53 | 54 | #### Basic use 55 | 56 | ```php 57 | return GraphQL::query(' 58 | capsules { 59 | id 60 | original_launch 61 | status 62 | missions { 63 | name 64 | flight 65 | } 66 | } 67 | ')->get(); 68 | //->get('json'); //get response as json object 69 | ``` 70 | 71 | #### Mutator Request 72 | 73 | ```php 74 | return GraphQL::mutator(' 75 | insert_user(name: "David") { 76 | id 77 | name 78 | date_added 79 | } 80 | ')->get(); 81 | //->get('json'); 82 | ``` 83 | 84 | You can access "query" or "mutator" as a shortcut if you are not passing variables, if is not the case you must use the "raw" attribute: 85 | 86 | ```php 87 | return GraphQL::raw(' 88 | mutation($name: String) { 89 | insert_user(name: $name) { 90 | id 91 | name 92 | date_added 93 | } 94 | } 95 | ') 96 | ->with(["name" => "David"]) 97 | ->get(); 98 | //->get('json'); 99 | ``` 100 | 101 | The `variables` or `payload` to the GraphQL request can also be passed using magic methods like: 102 | 103 | ```php 104 | return GraphQL::raw(' 105 | mutation($name: String) { 106 | insert_user(name: $name) { 107 | id 108 | name 109 | date_added 110 | } 111 | } 112 | ') 113 | ->withName("David") 114 | ->get(); 115 | //->get('json'); 116 | ``` 117 | 118 | #### Raw Response 119 | 120 | You can get the raw response from the GraphQL request by using `getRaw()` method instead of `get()` in the request. 121 | 122 | ```php 123 | return GraphQL::raw(' 124 | mutation($name: String) { 125 | insert_user(name: $name) { 126 | id 127 | name 128 | date_added 129 | } 130 | } 131 | ') 132 | ->with(["name" => "David"]) 133 | ->getRaw(); 134 | //->getRaw('json'); 135 | ``` 136 | 137 | If you want to address the request to another endpoint, you can do : 138 | 139 | ```php 140 | return GraphQL::endpoint("https://api.spacex.land/graphql/") 141 | ->query(' 142 | capsules { 143 | id 144 | original_launch 145 | status 146 | missions { 147 | name 148 | flight 149 | } 150 | } 151 | ')->get(); 152 | //->get('json'); 153 | ``` 154 | 155 | ## Headers 156 | 157 | You can include a header to the request by using the attribute "header" or add multiple headers by "withHeaders": 158 | ```php 159 | return GraphQL::query($query) 160 | ->header('name', 'value') 161 | ->withHeaders([ 162 | 'name' => 'value', 163 | 'name' => 'value' 164 | ])->get(); 165 | ``` 166 | 167 | ## Context 168 | 169 | Add additional context to the request 170 | ```php 171 | return GraphQL::query($query) 172 | ->context([ 173 | 'ssl' => [ 174 | "verify_peer" => false, 175 | "verify_peer_name" => false, 176 | ] 177 | ])->get(); 178 | ``` 179 | 180 | 181 | ## Author 182 | 183 | - David Gutierrez [@bendeckdavid](https://www.github.com/bendeckdavid) 184 | 185 | 186 | ## Top Contributors ⭐ 187 | 188 | - Ehsan Quddusi [@ehsanquddusi](https://github.com/ehsanquddusi) 189 | 190 | ## Contributors 191 | 192 | - Ryan Mayberry [@kerkness](https://github.com/kerkness) 193 | - Jamie Duong [@chiendv](https://github.com/chiendv) 194 | 195 | 196 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | ddbendeck@pm.me. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/Classes/Client.php: -------------------------------------------------------------------------------- 1 | 'application/json', 19 | 'User-Agent' => 'Laravel GraphQL client', 20 | ]; 21 | public array $context = []; 22 | 23 | public function __construct( 24 | protected string|null $endpoint 25 | ) 26 | { 27 | 28 | } 29 | 30 | /** 31 | * Generate the Graphql query in raw format 32 | * 33 | * @return string 34 | */ 35 | public function getRawQueryAttribute() 36 | { 37 | $content = match($this->queryType){ 38 | Request::RAW => $this->query, 39 | DEFAULT => "{$this->queryType} {{$this->query}}" 40 | }; 41 | 42 | return <<<"GRAPHQL" 43 | {$content} 44 | GRAPHQL; 45 | } 46 | 47 | 48 | /** 49 | * Build the HTTP request 50 | * 51 | * @return resource 52 | */ 53 | public function getRequestAttribute() 54 | { 55 | return stream_context_create(array_merge([ 56 | 'http' => [ 57 | 'method' => 'POST', 58 | 'content' => json_encode(['query' => $this->raw_query, 'variables' => $this->variables], JSON_NUMERIC_CHECK), 59 | 'header' => $this->headers, 60 | ] 61 | ], $this->context)); 62 | } 63 | 64 | 65 | /** 66 | * Include authentication headers 67 | * 68 | * @return void 69 | */ 70 | protected function includeAuthentication() 71 | { 72 | $auth_scheme = config('graphqlclient.auth_scheme'); 73 | 74 | // Check if is a valid authentication scheme 75 | if (!array_key_exists($auth_scheme, config('graphqlclient.auth_schemes'))) 76 | throw new Exception('Invalid Graphql Client Auth Scheme'); 77 | 78 | // fill Authentication header 79 | $authToken = isset($this->token) ? $this->token : config('graphqlclient.auth_credentials'); 80 | data_fill($this->rawHeaders, config('graphqlclient.auth_header'), 81 | config('graphqlclient.auth_schemes')[$auth_scheme].$authToken); 82 | } 83 | 84 | 85 | /** 86 | * Return Client headers formatted 87 | * 88 | * @return array 89 | */ 90 | public function getHeadersAttribute() 91 | { 92 | // Include Authentication 93 | if(config('graphqlclient.auth_credentials') || isset($this->token)) { 94 | $this->includeAuthentication(); 95 | } 96 | 97 | $formattedHeaders = []; 98 | foreach ($this->rawHeaders as $key => $value) { 99 | $formattedHeaders[] = $key . ': ' . $value; 100 | } 101 | 102 | return $formattedHeaders; 103 | } 104 | 105 | 106 | /** 107 | * Allow to append a new header to the client 108 | * 109 | * @return Client 110 | */ 111 | public function header(string $key, string $value) 112 | { 113 | $this->rawHeaders = array_merge($this->rawHeaders, [ 114 | $key => $value 115 | ]); 116 | 117 | return $this; 118 | } 119 | 120 | 121 | /** 122 | * Allow to append a new context info to the client 123 | * 124 | * @return Client 125 | */ 126 | public function context(array $context) 127 | { 128 | $this->context = $context; 129 | return $this; 130 | } 131 | 132 | 133 | /** 134 | * Allow to pass multiple headers to the client 135 | * 136 | * @return Client 137 | */ 138 | public function withHeaders(array $headers) 139 | { 140 | $this->rawHeaders = array_merge($this->rawHeaders, $headers); 141 | 142 | return $this; 143 | } 144 | 145 | 146 | /** 147 | * Allow to pass multiples variables to the client 148 | * 149 | * @return Client 150 | */ 151 | public function with(array $variables) 152 | { 153 | $this->variables = array_merge($this->variables, $variables); 154 | 155 | return $this; 156 | } 157 | 158 | 159 | /** 160 | * Build a new client 161 | * 162 | * @return Client 163 | */ 164 | private function generate(string $type, string $query) 165 | { 166 | $this->queryType = $type; 167 | $this->query = $query; 168 | 169 | return $this; 170 | } 171 | 172 | 173 | /** 174 | * Build a new Graphql Query request 175 | * 176 | * @return Client 177 | */ 178 | public function query(string $query) 179 | { 180 | return $this->generate(Request::QUERY, $query); 181 | } 182 | 183 | 184 | /** 185 | * Build a new Graphql Mutation request 186 | * 187 | * @return Client 188 | */ 189 | public function mutation(string $query) 190 | { 191 | return $this->generate(Request::MUTATION, $query); 192 | } 193 | 194 | 195 | /** 196 | * Build a new Graphql Raw request 197 | * 198 | * @return Client 199 | */ 200 | public function raw(string $query) 201 | { 202 | return $this->generate(Request::RAW, $query); 203 | } 204 | 205 | 206 | /** 207 | * Allow to change an request endpoint 208 | * 209 | * @return Client 210 | */ 211 | public function endpoint(string $endpoint) 212 | { 213 | $this->endpoint = $endpoint; 214 | 215 | return $this; 216 | } 217 | 218 | 219 | /** 220 | * Execute request 221 | * 222 | * @return array 223 | */ 224 | public function makeRequest(string $format, bool $rawResponse = false) 225 | { 226 | try { 227 | $result = file_get_contents($this->endpoint, false, $this->request); 228 | if ($format == Format::JSON) { 229 | $response = json_decode($result, false); 230 | if ($rawResponse) return $response; 231 | return $response->data; 232 | } else { 233 | $response = json_decode($result, true); 234 | if ($rawResponse) return $response; 235 | return Arr::get($response, "data"); 236 | } 237 | 238 | } catch (\Throwable $th) { 239 | throw $th; 240 | } 241 | } 242 | 243 | 244 | /** 245 | * Return data 246 | * @param $format string (array|json) define return format, array by default 247 | * 248 | * @return array by default 249 | */ 250 | public function get(string $format = Format::ARRAY) 251 | { 252 | return $this->makeRequest($format); 253 | } 254 | 255 | /** 256 | * Return raw response 257 | * @param $format string (array|json) define return format, array by default 258 | * 259 | * @return array by default 260 | */ 261 | public function getRaw(string $format = Format::ARRAY) 262 | { 263 | return $this->makeRequest($format, true); 264 | } 265 | 266 | } 267 | --------------------------------------------------------------------------------