├── .gitignore ├── assets ├── logo.png └── graphiql.png ├── docker-compose.yml ├── config └── graphql.php ├── phpunit.xml ├── tests ├── GraphiQLTest.php ├── Queries │ ├── HeroQuery.php │ └── HeroQueryPaginated.php ├── Mutations │ └── HeroMutation.php ├── HeroQueryTest.php ├── HeroMutationTest.php └── HeroQueryPaginatedTest.php ├── Dockerfile ├── src ├── TypeRegistry.php ├── ServiceProvider.php ├── SchemaFactory.php ├── Query.php ├── Mutation.php ├── GraphQLController.php └── QueryPaginated.php ├── composer.json ├── README.md └── resources └── views └── graphiql.blade.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .phpcs-cache 4 | .phpunit.result.cache 5 | .idea -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supliu/laravel-graphql/HEAD/assets/logo.png -------------------------------------------------------------------------------- /assets/graphiql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supliu/laravel-graphql/HEAD/assets/graphiql.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | laravel-graphql: 6 | build: . 7 | ports: 8 | - "8080:80" 9 | volumes: 10 | - .:/app:delegated 11 | - ~/.composer:/home/root/.composer:delegated -------------------------------------------------------------------------------- /config/graphql.php: -------------------------------------------------------------------------------- 1 | [ 14 | 15 | 'default' => [ 16 | 17 | 'queries' => [], 18 | 'mutations' => [] 19 | 20 | ] 21 | ] 22 | ]; -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ./tests/ 11 | 12 | 13 | 14 | 15 | 16 | ./src 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/GraphiQLTest.php: -------------------------------------------------------------------------------- 1 | call('GET', '/graphql'); 26 | 27 | $crawler->assertViewIs('laravel-graphql::graphiql'); 28 | } 29 | } -------------------------------------------------------------------------------- /tests/Queries/HeroQuery.php: -------------------------------------------------------------------------------- 1 | 'HeroQueryResult', 18 | 'fields' => [ 19 | 'name' => Type::string() 20 | ] 21 | ]); 22 | } 23 | 24 | /** 25 | * @return mixed 26 | */ 27 | protected function resolve($root, $args, $context, $info) 28 | { 29 | return [ 30 | 'name' => 'R2-D2' 31 | ]; 32 | } 33 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.4-apache 2 | 3 | WORKDIR /app 4 | 5 | RUN apt update 6 | 7 | RUN apt install -y git libzip-dev zip && \ 8 | docker-php-ext-install zip 9 | 10 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 11 | 12 | RUN a2enmod rewrite 13 | 14 | RUN rm -rf /var/www/html && \ 15 | ln -s /app /var/www/html 16 | 17 | RUN pecl install xdebug && docker-php-ext-enable xdebug 18 | 19 | RUN echo "xdebug.mode=off" | tee -a /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini > /dev/null && \ 20 | echo "xdebug.start_with_request=yes" | tee -a /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini > /dev/null && \ 21 | echo "xdebug.client_host=host.docker.internal" | tee -a /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini > /dev/null -------------------------------------------------------------------------------- /src/TypeRegistry.php: -------------------------------------------------------------------------------- 1 | name, self::$types)) 29 | self::$types[$type->name] = $type; 30 | 31 | return self::$types[$type->name]; 32 | } 33 | } -------------------------------------------------------------------------------- /tests/Queries/HeroQueryPaginated.php: -------------------------------------------------------------------------------- 1 | 'HeroQueryPaginatedResult', 18 | 'fields' => [ 19 | 'name' => Type::string() 20 | ] 21 | ]); 22 | } 23 | 24 | /** 25 | * @return mixed 26 | */ 27 | protected function resolve($root, $args, $context, $info) 28 | { 29 | return [ 30 | 'total' => 1, 31 | 'current_page' => 1, 32 | 'per_page' => 15, 33 | 'last_page' => 1, 34 | 'data' => [ 35 | [ 36 | 'name' => 'R2-D2' 37 | ] 38 | ] 39 | ]; 40 | } 41 | } -------------------------------------------------------------------------------- /tests/Mutations/HeroMutation.php: -------------------------------------------------------------------------------- 1 | 'UpdateHeroResult', 15 | 'fields' => [ 16 | 'hero_id' => Type::int(), 17 | 'error' => Type::boolean(), 18 | 'message' => Type::string() 19 | ] 20 | ]); 21 | } 22 | 23 | /** 24 | * @return array 25 | */ 26 | protected function args(): array 27 | { 28 | return [ 29 | 'id' => Type::nonNull(Type::int()) 30 | ]; 31 | } 32 | 33 | /** 34 | * @return mixed 35 | */ 36 | protected function resolve($root, $args, $context, $info) 37 | { 38 | return [ 39 | 'hero_id' => $args['id'], 40 | 'error' => false, 41 | 'message' => 'Updated!' 42 | ]; 43 | } 44 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supliu/laravel-graphql", 3 | "description": "GraphQL with Laravel Framework", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "laravel", "graphql", "api" 8 | ], 9 | "authors": [ 10 | { 11 | "name": "Jansen Felipe", 12 | "email": "jansen.felipe@gmail.com" 13 | } 14 | ], 15 | "autoload": { 16 | "psr-4": { 17 | "Supliu\\LaravelGraphQL\\": "src/" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Supliu\\LaravelGraphQL\\Tests\\": "tests/" 23 | } 24 | }, 25 | "require": { 26 | "webonyx/graphql-php": "14.*", 27 | "laravel/framework": "^7.30.4|^8.40|^9|~10" 28 | }, 29 | "require-dev": { 30 | "orchestra/testbench": "5.*" 31 | }, 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "1.0-dev" 35 | }, 36 | "laravel": { 37 | "providers": [ 38 | "Supliu\\LaravelGraphQL\\ServiceProvider" 39 | ] 40 | } 41 | }, 42 | "suggest": { 43 | "supliu/laravel-query-monitor": "Simple artisan command to monitoring triggered queries SQL" 44 | }, 45 | "minimum-stability": "dev" 46 | } 47 | -------------------------------------------------------------------------------- /tests/HeroQueryTest.php: -------------------------------------------------------------------------------- 1 | set('graphql.schemas.default.queries', [ 30 | 'hero' => \Supliu\LaravelGraphQL\Tests\Queries\HeroQuery::class 31 | ]); 32 | } 33 | 34 | /** 35 | * @test Query Hero 36 | */ 37 | public function testQueryHero(): void 38 | { 39 | $query = ' 40 | query HeroNameQuery { 41 | hero { 42 | name 43 | } 44 | } 45 | '; 46 | 47 | $crawler = $this->call('POST', '/graphql', ['query' => $query]); 48 | 49 | $crawler->assertJson([ 50 | 'data' => [ 51 | 'hero' => [ 52 | 'name' => 'R2-D2' 53 | ] 54 | ] 55 | ]); 56 | } 57 | } -------------------------------------------------------------------------------- /tests/HeroMutationTest.php: -------------------------------------------------------------------------------- 1 | set('graphql.schemas.default.mutations', [ 30 | 'hero' => \Supliu\LaravelGraphQL\Tests\Mutations\HeroMutation::class 31 | ]); 32 | } 33 | 34 | /** 35 | * @test Query Hero 36 | */ 37 | public function testQueryHero(): void 38 | { 39 | $query = ' 40 | mutation { 41 | hero (id: 1){ 42 | hero_id, 43 | error, 44 | message 45 | } 46 | } 47 | '; 48 | 49 | $crawler = $this->call('POST', '/graphql', ['query' => $query]); 50 | 51 | $crawler->assertJson([ 52 | 'data' => [ 53 | 'hero' => [ 54 | 'hero_id' => '1', 55 | 'error' => false, 56 | 'message' => 'Updated!' 57 | ] 58 | ] 59 | ]); 60 | } 61 | } -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 18 | __DIR__.'/../config/graphql.php', 'graphql' 19 | ); 20 | } 21 | 22 | /** 23 | * Bootstrap any application services. 24 | * 25 | * @return void 26 | */ 27 | public function boot() 28 | { 29 | /* 30 | * Load views 31 | */ 32 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'laravel-graphql'); 33 | 34 | /* 35 | * Publish config 36 | */ 37 | $this->publishes([ 38 | __DIR__.'/../config/graphql.php' => config_path('graphql.php') 39 | ], 'config'); 40 | 41 | /* 42 | * Publish view 43 | */ 44 | $this->publishes([ 45 | __DIR__.'/../resources/views' => base_path('resources/views/vendor/laravel-graphql'), 46 | ], 'views'); 47 | 48 | /* 49 | * Add routers 50 | */ 51 | $router = $this->app['router']; 52 | 53 | $router->get('/graphql', 'Supliu\LaravelGraphQL\GraphQLController@index'); 54 | $router->post('/graphql', 'Supliu\LaravelGraphQL\GraphQLController@executeQuery'); 55 | } 56 | 57 | protected function bootPublishes() 58 | { 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /src/SchemaFactory.php: -------------------------------------------------------------------------------- 1 | $this->createQuery($ref['queries']), 29 | 'mutation' => $this->createMutation($ref['mutations']) 30 | ]); 31 | } 32 | 33 | /** 34 | * @param array $queries 35 | * @return ObjectType 36 | */ 37 | private function createQuery(array $queries) 38 | { 39 | $fields = []; 40 | 41 | foreach ($queries as $key => $value) 42 | $fields[$key] = app()->make($value)->getConfig(); 43 | 44 | return new ObjectType([ 45 | 'name' => 'QueryRoot', 46 | 'fields' => $fields 47 | ]);; 48 | } 49 | 50 | /** 51 | * @param array $mutations 52 | * @return ObjectType 53 | */ 54 | private function createMutation(array $mutations) 55 | { 56 | $fields = []; 57 | 58 | foreach ($mutations as $key => $value) 59 | $fields[$key] = app()->make($value)->getConfig(); 60 | 61 | return new ObjectType([ 62 | 'name' => 'MutationRoot', 63 | 'fields' => $fields 64 | ]); 65 | } 66 | } -------------------------------------------------------------------------------- /tests/HeroQueryPaginatedTest.php: -------------------------------------------------------------------------------- 1 | set('graphql.schemas.default.queries', [ 30 | 'heros' => \Supliu\LaravelGraphQL\Tests\Queries\HeroQueryPaginated::class 31 | ]); 32 | } 33 | 34 | /** 35 | * @test Query Hero 36 | */ 37 | public function testQueryHero(): void 38 | { 39 | $query = ' 40 | query HeroNameQuery { 41 | heros { 42 | total 43 | current_page, 44 | per_page, 45 | last_page, 46 | data { 47 | name 48 | } 49 | } 50 | } 51 | '; 52 | 53 | $crawler = $this->call('POST', '/graphql', ['query' => $query]); 54 | 55 | $crawler->assertJson( [ 56 | 'data' => [ 57 | 'heros' => [ 58 | 'total' => 1, 59 | 'current_page' => 1, 60 | 'per_page' => 15, 61 | 'last_page' => 1, 62 | 'data' => [ 63 | [ 64 | 'name' => 'R2-D2' 65 | ] 66 | ] 67 | ] 68 | ] 69 | ]); 70 | } 71 | } -------------------------------------------------------------------------------- /src/Query.php: -------------------------------------------------------------------------------- 1 | config = [ 28 | 'type' => $this->typeResult(), 29 | 'args' => $this->resolveArgs(), 30 | 'resolve' => function ($root, $args, $context, $info) { 31 | 32 | if(!$this->authorize()) 33 | throw new UnauthorizedException("Unauthorized."); 34 | 35 | if(!empty($this->rules())) 36 | Validator::make($args, $this->rules())->validate(); 37 | 38 | return $this->resolve($root, $args, $context, $info); 39 | } 40 | ]; 41 | } 42 | 43 | /** 44 | * @return mixed 45 | */ 46 | protected abstract function resolve($root, $args, $context, $info); 47 | 48 | /** 49 | * @return boolean 50 | */ 51 | protected function authorize(): bool 52 | { 53 | return true; 54 | } 55 | 56 | /** 57 | * @return array 58 | */ 59 | protected function rules(): array 60 | { 61 | return []; 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | protected function args(): array 68 | { 69 | return []; 70 | } 71 | 72 | /** 73 | * @return Type 74 | */ 75 | protected function typeResult(): Type 76 | { 77 | return Type::boolean(); 78 | } 79 | 80 | /** 81 | * @return array 82 | */ 83 | private function resolveArgs(): array 84 | { 85 | $transformed = collect($this->args())->transform(function ($item, $key){ 86 | 87 | if(is_array($item)) 88 | return $item; 89 | 90 | return [ 91 | 'type' => $item, 92 | 'description' => $key 93 | ]; 94 | }); 95 | 96 | return $transformed->toArray(); 97 | } 98 | 99 | /** 100 | * @return array 101 | */ 102 | public function getConfig(): array 103 | { 104 | return $this->config; 105 | } 106 | } -------------------------------------------------------------------------------- /src/Mutation.php: -------------------------------------------------------------------------------- 1 | config = [ 22 | 'type' => $this->typeResult(), 23 | 'args' => $this->resolveArgs(), 24 | 'resolve' => $this->resolveFunction() 25 | ]; 26 | } 27 | 28 | /** 29 | * @return mixed 30 | */ 31 | protected abstract function resolve($root, $args, $context, $info); 32 | 33 | /** 34 | * @return boolean 35 | */ 36 | protected function authorize(): bool 37 | { 38 | return true; 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | protected function rules(): array 45 | { 46 | return []; 47 | } 48 | 49 | /** 50 | * @return array 51 | */ 52 | protected function args(): array 53 | { 54 | return []; 55 | } 56 | 57 | /** 58 | * @return Type 59 | */ 60 | protected function typeResult(): Type 61 | { 62 | return Type::boolean(); 63 | } 64 | 65 | /** 66 | * @return \Closure 67 | */ 68 | private function resolveFunction() 69 | { 70 | return function ($root, $args, $context, $info) { 71 | 72 | if(!$this->authorize()) 73 | throw new UnauthorizedException("Unauthorized."); 74 | 75 | if(!empty($this->rules())) 76 | Validator::make($args, $this->rules())->validate(); 77 | 78 | return $this->resolve($root, $args, $context, $info); 79 | }; 80 | } 81 | 82 | /** 83 | * @return array 84 | */ 85 | private function resolveArgs(): array 86 | { 87 | $transformed = collect($this->args())->transform(function ($item, $key){ 88 | 89 | if(is_array($item)) 90 | return $item; 91 | 92 | return [ 93 | 'type' => $item, 94 | 'description' => $key 95 | ]; 96 | }); 97 | 98 | return $transformed->toArray(); 99 | } 100 | 101 | /** 102 | * @return array 103 | */ 104 | public function getConfig(): array 105 | { 106 | return $this->config; 107 | } 108 | } -------------------------------------------------------------------------------- /src/GraphQLController.php: -------------------------------------------------------------------------------- 1 | get('schema', 'default'); 33 | 34 | /* 35 | * Get query 36 | */ 37 | $query = $request->get('query'); 38 | 39 | /* 40 | * Get variables 41 | */ 42 | $variables = $request->get('variables'); 43 | 44 | /* 45 | * Check operations 46 | */ 47 | if($request->has('operations')) { 48 | 49 | $operations = json_decode($request->get('operations'), true); 50 | 51 | $query = $operations['query']; 52 | $variables = $operations['variables']; 53 | } 54 | 55 | /* 56 | * Execute Query 57 | */ 58 | $result = $graphQL->executeQuery($schemaFactory->create($schema), $query, null, null, $variables); 59 | 60 | /* 61 | * Show error 62 | */ 63 | 64 | if(count($result->errors) > 0){ 65 | 66 | $errors = []; 67 | 68 | /* 69 | * Check previous and report 70 | */ 71 | foreach ($result->errors as $error){ 72 | 73 | $previous = $error->getPrevious(); 74 | 75 | if(!is_null($previous)){ 76 | 77 | report($previous); 78 | 79 | if($previous instanceof ValidationException) 80 | foreach ($previous->validator->errors()->all() as $row) 81 | $errors[] = ['message' => $row]; 82 | } 83 | 84 | $errors[] = ['message' => $error->getMessage()]; 85 | } 86 | 87 | return response()->json(['errors' => $errors]); 88 | } 89 | 90 | /* 91 | * To json 92 | */ 93 | return response()->json($result->toArray()); 94 | } 95 | } -------------------------------------------------------------------------------- /src/QueryPaginated.php: -------------------------------------------------------------------------------- 1 | config = [ 35 | 'type' => new ObjectType([ 36 | 'name' => $className . 'List', 37 | 'fields' => [ 38 | 'total' => Type::int(), 39 | 'current_page' => Type::int(), 40 | 'per_page' => Type::int(), 41 | 'last_page' => Type::int(), 42 | 'data' => Type::listOf($this->typeResult()) 43 | ] 44 | ]), 45 | 'args' => $this->resolveArgs(), 46 | 'resolve' => $this->resolveFunction() 47 | ]; 48 | } 49 | 50 | /** 51 | * @return mixed 52 | */ 53 | protected abstract function resolve($root, $args, $context, $info); 54 | 55 | /** 56 | * @return boolean 57 | */ 58 | protected function authorize(): bool 59 | { 60 | return true; 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | protected function rules(): array 67 | { 68 | return []; 69 | } 70 | 71 | /** 72 | * @return array 73 | */ 74 | protected function args(): array 75 | { 76 | return []; 77 | } 78 | 79 | /** 80 | * @return Type 81 | */ 82 | protected function typeResult(): Type 83 | { 84 | return Type::boolean(); 85 | } 86 | 87 | /** 88 | * @return \Closure 89 | */ 90 | private function resolveFunction() 91 | { 92 | return function ($root, $args, $context, $info) { 93 | 94 | if(!$this->authorize()) 95 | throw new UnauthorizedException("Unauthorized."); 96 | 97 | if(!empty($this->rules())) 98 | Validator::make($args, $this->rules())->validate(); 99 | 100 | $this->resolveInfo = $info; 101 | 102 | Paginator::currentPageResolver(function ($pageName = 'page') use ($args) { 103 | 104 | return isset($args[$pageName]) ? $args[$pageName] : 1; 105 | 106 | }); 107 | 108 | return $this->resolve($root, $args, $context, $info); 109 | }; 110 | } 111 | 112 | /** 113 | * @return array 114 | */ 115 | private function resolveArgs(): array 116 | { 117 | $args = array_merge($this->args(), [ 118 | 'page' => Type::int(), 119 | 'limit' => Type::int() 120 | ]); 121 | 122 | $transformed = collect($args)->transform(function ($item, $key){ 123 | 124 | if(is_array($item)) 125 | return $item; 126 | 127 | return [ 128 | 'type' => $item, 129 | 'description' => $key 130 | ]; 131 | }); 132 | 133 | return $transformed->toArray(); 134 | } 135 | 136 | /** 137 | * @return array 138 | */ 139 | public function getConfig(): array 140 | { 141 | return $this->config; 142 | } 143 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Laravel GraphQL 6 | 7 | [![Latest Stable Version](https://poser.pugx.org/supliu/laravel-graphql/v)](//packagist.org/packages/supliu/laravel-graphql) [![Total Downloads](https://poser.pugx.org/supliu/laravel-graphql/downloads)](//packagist.org/packages/supliu/laravel-graphql) [![Latest Unstable Version](https://poser.pugx.org/supliu/laravel-graphql/v/unstable)](//packagist.org/packages/supliu/laravel-graphql) [![License](https://poser.pugx.org/supliu/laravel-graphql/license)](//packagist.org/packages/phpunit/phpunit) 8 | 9 | The objective of this project is to facilitate the integration of the webonyx/graphql-php with the Laravel Framework 10 | 11 |

12 | 13 |

14 | 15 | ## How to install 16 | 17 | Use composer to install this package 18 | 19 | ```ssh 20 | composer require supliu/laravel-graphql 21 | ``` 22 | 23 | Execute a publish with artisan command: 24 | 25 | ``` 26 | php artisan vendor:publish --provider="Supliu\LaravelGraphQL\ServiceProvider" 27 | ``` 28 | 29 | ## How to use 30 | 31 | You must create your Query and Mutation classes and register on `config/graphql.php` so that GraphQL can read. 32 | 33 | ```php 34 | 'queries' => [ 35 | 'detailHero' => \App\GraphQL\Queries\DetailHero::class 36 | ], 37 | 38 | 'mutations' => [ 39 | 'updateHero' => \App\GraphQL\Mutations\UpdateHero::class 40 | ] 41 | ``` 42 | 43 | ### Query 44 | 45 | Below is an example of a Query class that returns the data of a Star Wars hero: 46 | 47 | ```php 48 | Type::nonNull(Type::int()) 65 | ]; 66 | } 67 | 68 | /** 69 | * @return Type 70 | */ 71 | protected function typeResult(): Type 72 | { 73 | return new ObjectType([ 74 | 'name' => 'HeroQueryResult', 75 | 'fields' => [ 76 | 'name' => Type::string() 77 | ] 78 | ]); 79 | } 80 | 81 | /** 82 | * @return mixed 83 | */ 84 | protected function resolve($root, $args, $context, $info) 85 | { 86 | return Hero::find($args['id']); 87 | } 88 | } 89 | ``` 90 | 91 | ### Mutation 92 | 93 | Below is an example of a Mutation class that returns if update worked: 94 | 95 | ```php 96 | 'UpdateHeroResult', 110 | 'fields' => [ 111 | 'error' => Type::boolean(), 112 | 'message' => Type::string() 113 | ] 114 | ]); 115 | } 116 | 117 | /** 118 | * @return array 119 | */ 120 | protected function args(): array 121 | { 122 | return [ 123 | 'id' => Type::nonNull(Type::int()) 124 | 'name' => Type::nonNull(Type::string()) 125 | ]; 126 | } 127 | 128 | /** 129 | * @return mixed 130 | */ 131 | protected function resolve($root, $args, $context, $info) 132 | { 133 | Hero::find($args['id'])->update([ 134 | 'name' => $args['name'] 135 | ]); 136 | 137 | return [ 138 | 'error' => false, 139 | 'message' => 'Updated!' 140 | ]; 141 | } 142 | } 143 | ``` 144 | 145 | ## License 146 | 147 | The Laravel GraphQL is open-sourced project licensed under the [MIT license](https://opensource.org/licenses/MIT). 148 | -------------------------------------------------------------------------------- /resources/views/graphiql.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 44 | 51 | 52 | 53 | 54 | 55 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 | 79 |
80 |
81 | 82 | 83 | 84 |
Loading...
85 | 232 | 233 | --------------------------------------------------------------------------------