├── .gitignore ├── src ├── NaturalLanguageFacade.php ├── Verdict.php ├── NaturalLanguageServiceProvider.php ├── NaturalLanguageClient.php └── NaturalLanguage.php ├── phpunit.xml.dist ├── config └── naturallanguage.php ├── LICENSE.txt ├── google.md ├── .github └── workflows │ └── run-tests.yml ├── CHANGELOG.md ├── composer.json ├── README.md └── tests └── NaturalLanguageTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .DS_Store 4 | Thumbs.db 5 | phpunit.xml 6 | /.idea -------------------------------------------------------------------------------- /src/NaturalLanguageFacade.php: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | ./tests 12 | 13 | 14 | 15 | 16 | ./src 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/NaturalLanguageServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 12 | __DIR__ . '/../config/naturallanguage.php' => config_path('naturallanguage.php'), 13 | ]); 14 | } 15 | 16 | public function register() 17 | { 18 | $this->mergeConfigFrom(__DIR__ . '/../config/naturallanguage.php', 'naturallanguage'); 19 | 20 | $this->app->bind(NaturalLanguageClient::class, function () { 21 | return new NaturalLanguageClient(config('naturallanguage')); 22 | }); 23 | 24 | $this->app->bind(NaturalLanguage::class, function () { 25 | $client = app(NaturalLanguageClient::class); 26 | 27 | return new NaturalLanguage($client); 28 | }); 29 | 30 | $this->app->alias(NaturalLanguage::class, 'laravel-natural-language'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /config/naturallanguage.php: -------------------------------------------------------------------------------- 1 | env('NATURAL_LANGUAGE_PROJECT_ID', 'sample-12345'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Path to the json file containing the authentication credentials. 22 | |-------------------------------------------------------------------------- 23 | */ 24 | 'key_file_path' => base_path('composer.json'), 25 | ]; 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Jogg Inc 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /google.md: -------------------------------------------------------------------------------- 1 | - After logging in the Google Cloud Platform console you will need to enable the Cloud Natural Language API via the API Library section. 2 | 3 | - Now, we first need to create a new project in the Google Cloud Platform console. 4 | 5 | - You will see a screen like this once you click on "New Project" on the dashoard: 6 | 7 | screen shot 2018-10-11 at 12 11 19 am 8 | 9 | - After creating the project, please note down the **Project ID** and add it to the `.env` file for the key `NATURAL_LANGUAGE_PROJECT_ID`. 10 | 11 | - Select the project you have created, and go the "Create Service Account Key" page and in the 'Service Account' section click on 'New Service Account'. 12 | 13 | - Enter the name & select the Role as 'Owner' for the project. 14 | 15 | - Then click on create to have the JSON credentials file downloaded automatically. 16 | 17 | - Add that json file in your laravel project root & add it to `.gitignore`. 18 | 19 | - Set the path to that file as the value for the key `key_file_path` in the `config/naturallanguage.php` (config file published by this package). -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: "Run Tests" 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: true 11 | matrix: 12 | php: [8.1] 13 | laravel: [10.*] 14 | dependency-version: [prefer-lowest, prefer-stable] 15 | 16 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v2 21 | 22 | - name: Cache dependencies 23 | uses: actions/cache@v2 24 | with: 25 | path: ~/.composer/cache/files 26 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 27 | 28 | - name: Setup PHP 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: ${{ matrix.php }} 32 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick 33 | coverage: none 34 | 35 | - name: Install dependencies 36 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 37 | 38 | - name: Execute tests 39 | run: vendor/bin/phpunit -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | All the notable changes to the Laravel Natural Language package are documented in this file: 2 | 3 | ## v7.0.0 (11-02-2022) 4 | - Added Laravel 9 support and dropped support for Laravel 7 and 8, and PHP 7. 5 | 6 | ## v6.0.1 (06-01-2021) 7 | - Add PHP 8 support. 8 | 9 | ## v6.0.0 (28-09-2020) 10 | - Updated the underlying google cloud language sdk 11 | 12 | ## v5.0.0 (10-09-2020) 13 | - Added Laravel v8.x support. 14 | - Dropped Laravel v6 & PHPUnit v8 support. 15 | 16 | ## v4.0.0 (03-03-2020) 17 | - Added Laravel v7.x support. 18 | - Dropped PHP v7.2 support, now PHP v7.3 is the minimum requirement. 19 | 20 | ## v3.0.1 (14-01-2020) 21 | - Added Github Actions workflow 22 | 23 | ## v3.0.0 (04-09-2019) 24 | - Now supports Laravel v6.0 25 | - Support for Laravel v5.8 will be dropped in v3.1 26 | 27 | ## v2.1.0 (20-08-2019) 28 | - Update google-cloud-language sdk 29 | 30 | ## v2.0.1 (27-02-2019) 31 | - Remove phpunit deprications 32 | 33 | ## v2.0.0 (27-02-2019) 34 | - Now supports Laravel v5.8 35 | 36 | ## v1.0.2 (13-10-2018) 37 | - [Refactor](https://github.com/JoggApp/laravel-natural-language/pull/3) 38 | - Thanks to [Sven](https://github.com/svenluijten) for the PR :smile: 39 | 40 | ## v1.0.1 (12-10-2018) 41 | - [Typo fix](https://github.com/JoggApp/laravel-natural-language/pull/1) 42 | 43 | ## v1.0.0 (11-10-2018) 44 | - Initial release 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "joggapp/laravel-natural-language", 3 | "description": "Laravel package for the Google Natural language API", 4 | "keywords": [ 5 | "laravel", 6 | "package", 7 | "google", 8 | "natural language", 9 | "api" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Harish Toshniwal", 15 | "email": "harish@jogg.co" 16 | } 17 | ], 18 | "autoload": { 19 | "psr-4": { 20 | "JoggApp\\NaturalLanguage\\": "src" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "JoggApp\\NaturalLanguage\\Tests\\": "tests" 26 | } 27 | }, 28 | "require": { 29 | "php": "^8.1", 30 | "google/cloud-language": "^0.28.0", 31 | "illuminate/support": "10.*" 32 | }, 33 | "require-dev": { 34 | "mockery/mockery": "^1.1", 35 | "orchestra/testbench" : "8.*", 36 | "phpunit/phpunit": "^9.0" 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "JoggApp\\NaturalLanguage\\NaturalLanguageServiceProvider" 42 | ], 43 | "aliases":{ 44 | "NaturalLanguage": "JoggApp\\NaturalLanguage\\NaturalLanguageFacade" 45 | } 46 | } 47 | }, 48 | "config": { 49 | "sort-packages": true 50 | }, 51 | "minimum-stability": "dev", 52 | "prefer-stable": true 53 | } 54 | -------------------------------------------------------------------------------- /src/NaturalLanguageClient.php: -------------------------------------------------------------------------------- 1 | checkForInvalidConfiguration($config); 15 | 16 | $this->language = new LanguageClient([ 17 | 'keyFilePath' => $config['key_file_path'], 18 | 'projectId' => $config['project_id'] 19 | ]); 20 | } 21 | 22 | public function sentiment(string $text) 23 | { 24 | return $this->language 25 | ->analyzeSentiment($text) 26 | ->sentiment(); 27 | } 28 | 29 | public function entities(string $text) 30 | { 31 | return $this->language 32 | ->analyzeEntities($text) 33 | ->entities(); 34 | } 35 | 36 | public function entitySentiment(string $text) 37 | { 38 | return $this->language 39 | ->analyzeEntitySentiment($text) 40 | ->entities(); 41 | } 42 | 43 | public function syntax(string $text) 44 | { 45 | return $this 46 | ->language 47 | ->analyzeSyntax($text); 48 | } 49 | 50 | public function categories(string $text) 51 | { 52 | return $this->language 53 | ->classifyText($text) 54 | ->categories(); 55 | } 56 | 57 | public function annotateText(string $text, array $features = []) 58 | { 59 | return empty($features) 60 | ? $this->language->annotateText($text) 61 | : $this->language->annotateText($text, ['features' => $features]); 62 | } 63 | 64 | private function checkForInvalidConfiguration(array $config) 65 | { 66 | if (!file_exists($config['key_file_path'])) { 67 | throw new Exception('The json file does not exist at the given path'); 68 | } 69 | 70 | if ((!is_string($config['project_id'])) || empty($config['project_id'])) { 71 | throw new Exception('Please set a valid project id'); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/NaturalLanguage.php: -------------------------------------------------------------------------------- 1 | languageClient = $client; 12 | } 13 | 14 | public function sentiment(string $text): array 15 | { 16 | $sentiment = $this 17 | ->languageClient 18 | ->sentiment($text); 19 | 20 | $verdict = $this->prepareVerdict($sentiment['score'], $sentiment['magnitude']); 21 | 22 | return [ 23 | 'text' => $text, 24 | 'verdict' => $verdict, 25 | 'score' => $sentiment['score'], 26 | 'magnitude' => $sentiment['magnitude'] 27 | ]; 28 | } 29 | 30 | public function entities(string $text): array 31 | { 32 | $entities = $this 33 | ->languageClient 34 | ->entities($text); 35 | 36 | return [ 37 | 'text' => $text, 38 | 'entities' => $entities 39 | ]; 40 | } 41 | 42 | public function entitySentiment(string $text): array 43 | { 44 | $entities = $this 45 | ->languageClient 46 | ->entitySentiment($text); 47 | 48 | return [ 49 | 'text' => $text, 50 | 'entities' => $entities 51 | ]; 52 | } 53 | 54 | public function syntax(string $text): array 55 | { 56 | $syntax = $this 57 | ->languageClient 58 | ->syntax($text); 59 | 60 | return [ 61 | 'text' => $text, 62 | 'sentences' => method_exists($syntax, 'sentences') ? $syntax->sentences() : null, 63 | 'tokens' => method_exists($syntax, 'tokens') ? $syntax->tokens() : null, 64 | 'language' => method_exists($syntax, 'language') ? $syntax->language() : null 65 | ]; 66 | } 67 | 68 | public function categories(string $text): array 69 | { 70 | $categories = $this 71 | ->languageClient 72 | ->categories($text); 73 | 74 | return [ 75 | 'text' => $text, 76 | 'categories' => $categories 77 | ]; 78 | } 79 | 80 | public function annotateText(string $text, array $features = []): array 81 | { 82 | $annotation = $this 83 | ->languageClient 84 | ->annotateText($text, $features); 85 | 86 | if (is_null($annotation)) { 87 | return [ 88 | 'text' => $text, 89 | 'sentences' => null, 90 | 'tokens' => null, 91 | 'entities' => null, 92 | 'sentiment' => null, 93 | 'categories' => null, 94 | 'language' => null, 95 | ]; 96 | } 97 | 98 | return [ 99 | 'text' => $text, 100 | 'sentences' => method_exists($annotation, 'sentences') ? $annotation->sentences() : null, 101 | 'tokens' => method_exists($annotation, 'tokens') ? $annotation->tokens() : null, 102 | 'entities' => method_exists($annotation, 'entities') ? $annotation->entities() : null, 103 | 'sentiment' => method_exists($annotation, 'sentiment') ? $annotation->sentiment() : null, 104 | 'categories' => method_exists($annotation, 'categories') ? $annotation->categories() : null, 105 | 'language' => method_exists($annotation, 'language') ? $annotation->language() : null, 106 | ]; 107 | } 108 | 109 | public function prepareVerdict(float $score, float $magnitude): string 110 | { 111 | if (in_array($score, range(-1.0, -0.25, 0.01))) { 112 | if (in_array($magnitude, range(1.0, 100.0, 0.01))) { 113 | return Verdict::VERY_NEGATIVE; 114 | } 115 | 116 | return Verdict::MOSTLY_NEGATIVE; 117 | } 118 | 119 | if (in_array($score, range(0.25, 1.0, 0.01))) { 120 | if (in_array($magnitude, range(1.0, 100.0, 0.01))) { 121 | return Verdict::VERY_POSITIVE; 122 | } 123 | 124 | return Verdict::MOSTLY_POSITIVE; 125 | } 126 | 127 | return Verdict::MIXED_AND_NEUTRAL; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PACKAGE IS NO LONGER ACTIVELY MAINTAINED. USERS ARE ADVISED TO REPLACE IT WITH ALTERNATIVES. 2 | 3 | # Laravel package for the Google Natural language API 4 | 5 | [![Latest Version](https://img.shields.io/github/release/JoggApp/laravel-natural-language.svg?style=flat-rounded)](https://github.com/JoggApp/laravel-natural-language/releases) 6 | ![](https://github.com/JoggApp/laravel-natural-language/workflows/Run%20Tests/badge.svg?branch=master) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/JoggApp/laravel-natural-language.svg?style=flat-rounded&colorB=brightgreen)](https://packagist.org/packages/JoggApp/laravel-natural-language) 8 | 9 | This package makes using the Google Natural API in your laravel app a breeze with minimum to no configuration, clean syntax and a consistent package API. All methods accept a string and return an array: [Docs below.](https://github.com/JoggApp/laravel-natural-language/#how-to-use) 10 | 11 | ![natural](https://user-images.githubusercontent.com/11228182/46806140-765d4000-cd84-11e8-9d88-e71338d53376.png) 12 | 13 | ## Installation 14 | 15 | - You can install this package via composer using this command: 16 | 17 | ```bash 18 | composer require joggapp/laravel-natural-language 19 | ``` 20 | 21 | - The package will automatically register itself. 22 | 23 | - We have documented how to setup the project and get the necessary configurations from the Google Cloud Platform console in a step by step detailed manner [over here.](https://github.com/JoggApp/laravel-natural-language/blob/master/google.md) 24 | 25 | - You can publish the config file using the following command: 26 | 27 | ```bash 28 | php artisan vendor:publish --provider="JoggApp\NaturalLanguage\NaturalLanguageServiceProvider" 29 | ``` 30 | 31 | This will create the package's config file called `naturallanguage.php` in the `config` directory. These are the contents of the published config file: 32 | 33 | ```php 34 | return [ 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | The id of project created in the Google Cloud Platform console. 38 | |-------------------------------------------------------------------------- 39 | */ 40 | 'project_id' => env('NATURAL_LANGUAGE_PROJECT_ID', 'sample-12345'), 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Path to the json file containing the authentication credentials. 45 | |-------------------------------------------------------------------------- 46 | */ 47 | 'key_file_path' => base_path('composer.json'), 48 | ]; 49 | ``` 50 | 51 | ## How to use 52 | 53 | - After setting up the config file values you are all set to use the following methods :smile: 54 | 55 | - Detect the Sentiment: Accepts a string and returns an array. 56 | 57 | ```php 58 | NaturalLanguage::sentiment(string $text): array 59 | ``` 60 | 61 | - Detect the Entities: Accepts a string and returns an array. 62 | 63 | ```php 64 | NaturalLanguage::entities(string $text): array 65 | ``` 66 | 67 | - Detect the Sentiment per entity basis: Accepts a string and returns an array. 68 | 69 | ```php 70 | NaturalLanguage::entitySentiment(string $text): array 71 | ``` 72 | 73 | - Detect the syntax: Accepts a string and returns an array. 74 | 75 | ```php 76 | NaturalLanguage::syntax(string $text): array 77 | ``` 78 | 79 | - Detect the categories: Accepts a string and returns an array. 80 | 81 | ```php 82 | NaturalLanguage::categories(string $text): array 83 | ``` 84 | 85 | - Annotate text: Accepts a string and an optional `features` array & returns an array. 86 | 87 | ```php 88 | NaturalLanguage::annotateText(string $text, array $features = ['sentiment', 'syntax']): array 89 | ``` 90 | 91 | ## Testing 92 | 93 | You can run the tests with: 94 | 95 | ```bash 96 | vendor/bin/phpunit 97 | ``` 98 | 99 | ## Changelog 100 | 101 | Please see the [CHANGELOG](CHANGELOG.md) for more information about what has changed recently. 102 | 103 | ## Security 104 | 105 | If you discover any security related issues, please email them to [harish@jogg.co](mailto:harish@jogg.co) instead of using the issue tracker. 106 | 107 | ## Credits 108 | 109 | - [Harish Toshniwal](https://github.com/introwit) 110 | - [All Contributors](../../contributors) 111 | 112 | ## License 113 | 114 | The MIT License (MIT). Please see the [License File](LICENSE.txt) for more information. 115 | -------------------------------------------------------------------------------- /tests/NaturalLanguageTest.php: -------------------------------------------------------------------------------- 1 | languageClient = Mockery::mock(NaturalLanguageClient::class); 25 | 26 | $this->language = new NaturalLanguage($this->languageClient); 27 | } 28 | 29 | public function tearDown(): void 30 | { 31 | Mockery::close(); 32 | } 33 | 34 | /** @test */ 35 | public function it_can_detect_the_sentiment_of_string_passed_to_it() 36 | { 37 | $this->languageClient 38 | ->shouldReceive('sentiment')->with($this->testString) 39 | ->once() 40 | ->andReturn(['score' => 0.8, 'magnitude' => 0.8]); 41 | 42 | $response = $this->language->sentiment($this->testString); 43 | 44 | $this->assertIsArray($response); 45 | 46 | $this->assertArrayHasKey('text', $response); 47 | $this->assertArrayHasKey('verdict', $response); 48 | $this->assertArrayHasKey('score', $response); 49 | $this->assertArrayHasKey('magnitude', $response); 50 | } 51 | 52 | /** @test */ 53 | public function it_gives_the_correct_verdict_based_on_the_score_and_magnitude_of_the_sentiment_of_a_string() 54 | { 55 | $verdict = $this->language->prepareVerdict(0.8, 10); 56 | $this->assertEquals(Verdict::VERY_POSITIVE, $verdict); 57 | 58 | $verdict = $this->language->prepareVerdict(-0.8, 10); 59 | $this->assertEquals(Verdict::VERY_NEGATIVE, $verdict); 60 | 61 | $verdict = $this->language->prepareVerdict(0.8, 0.10); 62 | $this->assertEquals(Verdict::MOSTLY_POSITIVE, $verdict); 63 | 64 | $verdict = $this->language->prepareVerdict(-0.8, 0.10); 65 | $this->assertEquals(Verdict::MOSTLY_NEGATIVE, $verdict); 66 | 67 | $verdict = $this->language->prepareVerdict(0.0, 0.0); 68 | $this->assertEquals(Verdict::MIXED_AND_NEUTRAL, $verdict); 69 | } 70 | 71 | /** @test */ 72 | public function it_can_detect_the_entities_from_the_string_passed_to_it() 73 | { 74 | $this->languageClient 75 | ->shouldReceive('entities')->with($this->testString) 76 | ->once() 77 | ->andReturn([]); 78 | 79 | $response = $this->language->entities($this->testString); 80 | 81 | $this->assertIsArray($response); 82 | 83 | $this->assertArrayHasKey('text', $response); 84 | $this->assertArrayHasKey('entities', $response); 85 | } 86 | 87 | /** @test */ 88 | public function it_can_detect_the_sentiment_per_entity_from_the_string_passed_to_it() 89 | { 90 | $this->languageClient 91 | ->shouldReceive('entitySentiment')->with($this->testString) 92 | ->once() 93 | ->andReturn([]); 94 | 95 | $response = $this->language->entitySentiment($this->testString); 96 | 97 | $this->assertIsArray($response); 98 | 99 | $this->assertArrayHasKey('text', $response); 100 | $this->assertArrayHasKey('entities', $response); 101 | } 102 | 103 | /** @test */ 104 | public function it_can_detect_the_syntax_from_the_string_passed_to_it() 105 | { 106 | $this->languageClient 107 | ->shouldReceive('syntax')->with($this->testString) 108 | ->once() 109 | ->andReturn((new Annotation)); 110 | 111 | $response = $this->language->syntax($this->testString); 112 | 113 | $this->assertIsArray($response); 114 | 115 | $this->assertArrayHasKey('text', $response); 116 | $this->assertArrayHasKey('sentences', $response); 117 | $this->assertArrayHasKey('tokens', $response); 118 | $this->assertArrayHasKey('language', $response); 119 | } 120 | 121 | /** @test */ 122 | public function it_can_detect_the_categories_from_the_string_passed_to_it() 123 | { 124 | $this->languageClient 125 | ->shouldReceive('categories')->with($this->testString) 126 | ->once() 127 | ->andReturn([]); 128 | 129 | $response = $this->language->categories($this->testString); 130 | 131 | $this->assertIsArray($response); 132 | 133 | $this->assertArrayHasKey('text', $response); 134 | $this->assertArrayHasKey('categories', $response); 135 | } 136 | 137 | /** @test */ 138 | public function it_can_annotate_the_string_passed_to_it() 139 | { 140 | $this->languageClient 141 | ->shouldReceive('annotateText')->withArgs([$this->testString, []]) 142 | ->once() 143 | ->andReturn(); 144 | 145 | $response = $this->language->annotateText($this->testString); 146 | 147 | $this->assertIsArray($response); 148 | 149 | $this->assertArrayHasKey('text', $response); 150 | $this->assertArrayHasKey('sentences', $response); 151 | $this->assertArrayHasKey('tokens', $response); 152 | $this->assertArrayHasKey('entities', $response); 153 | $this->assertArrayHasKey('sentiment', $response); 154 | $this->assertArrayHasKey('categories', $response); 155 | $this->assertArrayHasKey('language', $response); 156 | } 157 | } 158 | --------------------------------------------------------------------------------