Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
50cfb6c
Updated NationalCloud tests and Exception tests
Ndiritu Jul 12, 2021
e20238b
Add AbstractGraphClient tests
Ndiritu Jul 12, 2021
c18bf80
Add GraphRequest tests
Ndiritu Jul 26, 2021
ed45e10
Fix GraphCollectionRequest tests
Ndiritu Aug 4, 2021
7a58e0f
Fix GraphRequestUtil tests
Ndiritu Aug 4, 2021
1c54d1d
Fix GraphResponse tests
Ndiritu Aug 4, 2021
f29255a
Remove unnecessary test class
Ndiritu Aug 4, 2021
5e29901
Minor fixes to failing tests
Ndiritu Aug 4, 2021
10c3b87
Fix merge conflicts
Ndiritu Aug 4, 2021
cbcb26f
Use existing test data models
Ndiritu Aug 4, 2021
908be4e
Minor tweaks
Ndiritu Aug 4, 2021
9fbe9e9
Merge branch 'philip/feat/graph-request-changes' into philip/fix/tests
Ndiritu Aug 4, 2021
807ae85
Use phpstan/phpstan
Ndiritu Aug 10, 2021
354ac97
Make all full endpoint urls valid
Ndiritu Aug 10, 2021
160a67b
Prevent sending tokens to non-graph endpoints
Ndiritu Aug 10, 2021
a5b2578
Refactor validation of base urls and national cloud host urls
Ndiritu Aug 10, 2021
3629482
Fix failing Graph request constructor test
Ndiritu Aug 11, 2021
aeb7c4e
Support only StreamInterface as valid stream return type
Ndiritu Aug 11, 2021
b4855fb
Add code coverage output to validation workflow
Ndiritu Aug 16, 2021
d0b9097
Resolve merge conflicts
Ndiritu Aug 18, 2021
e2ce12d
Address static analysis issues
Ndiritu Aug 19, 2021
af3f36b
Add upgrade guide
Ndiritu Aug 16, 2021
147bb24
Update sdk version header
Ndiritu Aug 31, 2021
c242188
minor updates
Ndiritu Sep 1, 2021
caa15e9
Merge pull request #16 from microsoftgraph/fix/sdk-version-header
Ndiritu Sep 2, 2021
3d79614
Address PR feedback
Ndiritu Sep 14, 2021
9cabb6f
Merge branch 'philip/fix/tests' of github.com:microsoftgraph/msgraph-…
Ndiritu Sep 14, 2021
8d4e9f3
Update page iterator story + link highlights to description sections
Ndiritu Sep 14, 2021
fff1d93
Merge pull request #7 from microsoftgraph/feat/upgrade-guide
Ndiritu Sep 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# top-most EditorConfig file
root = true
# All PHP files MUST use the Unix LF (linefeed) line ending.
# Code MUST use an indent of 4 spaces, and MUST NOT use tabs for indenting.
# All PHP files MUST end with a single blank line.
# There MUST NOT be trailing whitespace at the end of non-blank lines.
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# PHP-Files, Composer.json, MD-Files
[{*.php,composer.json,*.md}]
indent_style = space
indent_size = 4
# HTML-Files LESS-Files SASS-Files CSS-Files JS-Files JSON-Files
[{*.html,*.less,*.sass,*.css,*.js,*.json}]
indent_style = tab
indent_size = 4
# Gitlab-CI, Travis-CI
[*.yml]
indent_style = space
indent_size = 2
# top-most EditorConfig file
root = true

# All PHP files MUST use the Unix LF (linefeed) line ending.
# Code MUST use an indent of 4 spaces, and MUST NOT use tabs for indenting.
# All PHP files MUST end with a single blank line.
# There MUST NOT be trailing whitespace at the end of non-blank lines.
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

# PHP-Files, Composer.json, MD-Files
[{*.php,composer.json,*.md}]
indent_style = space
indent_size = 4

# HTML-Files LESS-Files SASS-Files CSS-Files JS-Files JSON-Files
[{*.html,*.less,*.sass,*.css,*.js,*.json}]
indent_style = tab
indent_size = 4

# Gitlab-CI, Travis-CI
[*.yml]
indent_style = space
indent_size = 2
9 changes: 7 additions & 2 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- name: Setup PHP and Xdebug for Code Coverage report
uses: shivammathur/setup-php@v2
with:
php-version: '7.3'
coverage: xdebug
- name: Validate composer file
run: |
composer validate
Expand All @@ -28,7 +33,7 @@ jobs:
composer install
- name: Run tests
run : |
vendor/bin/phpunit --exclude-group functional
vendor/bin/phpunit --coverage-text
run-static-analysis:
runs-on: ubuntu-latest
steps:
Expand All @@ -39,6 +44,6 @@ jobs:
- name: Composer install
run: |
composer install
- name: Run static analysis
- name: Run static analysis
run: |
vendor/bin/phpstan analyse --memory-limit=500M --error-format=github
188 changes: 188 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Microsoft Graph PHP SDK Upgrade Guide

This guide highlights backward compatibility breaking changes introduced during major upgrades.


## 1.x to 2.0

Version `2.0` highlights:
- [Support for National Clouds.](#support-for-national-clouds)
- Changes in [creating a Graph client.](#creating-a-graph-client)
- Changes in [configuring your HTTP client](#configuring-http-clients-for-use-with-the-graph-api) (including support for PSR-18 and HTTPlug's HttpAsyncClient implementations).
- Introducing standardised Graph exception types [`GraphClientException`](#introducing-the-graphclientexception) and [`GraphServiceException`](#introducing-the-graphserviceexception) as more specific `GraphException` types.
- [`GraphRequest`](#graphrequest-changes) and [`GraphCollectionRequest`](#graphcollectionrequest-changes):
- PSR compliance & other standardisation efforts in request classes and methods.
- Throwing `Psr\Http\Client\ClientExceptionInterface` instead of `\GuzzleHttp\Exception\GuzzleException` in request methods.
- Accepting and returning `Psr\Http\Message\StreamInterface` request bodies instead of `GuzzleHttp\Psr7\Stream`.
- Allow overwriting the default Guzzle client with `Psr\Http\Client\ClientExceptionInterface` for synchronous requests.
- Allow overwriting the default Guzzle client with HTTPlug's `Http\Client\HttpAsyncClient` for asynchronous requests.
- [Introduces a resumable `PageIterator`](#introducing-the-pageiterator) that asynchronously pages through a collection response payload while running a custom callback function against each entity.
- Deprecates support for Guzzle `^6.0`.
- Strongly typed method parameters and return type declarations where possible.

### Support for National Clouds
We have introduced `NationalCloud` containing Microsoft Graph API endpoint constants to enable you to easily
set base URLs and in future authenticate against the various supported National Clouds.


### Creating a Graph client
- Version 2 deprecates setting HTTP-specific config via methods e.g. `setProxyPort()`.
- Deprecates `setBaseUrl()` and `setApiVersion()` in favour of passing these into the constructor.
The public cloud endpoint, `https://graph.microsoft.com`, will be set as the default base URL.
- By default, the SDK will create a Guzzle HTTP client using our default config.
The HTTP client can be customised as shown in the next section.


```php
$graphClient = new Graph(); // uses https://graph.microsoft.com as base URL and a Guzzle client as defaults
$response = $graphClient->setAccessToken("abc")
->setReturnType(Model\User::class)
->createRequest("GET", "/me")
->execute();
```


### Configuring HTTP clients for use with the Graph API
We now support use of any HTTP client library that implements PSR-18 and HTTPlug's HttpAsyncClient interfaces.
In addition, we provide a `HttpClientInterface` that you can implement with your HTTP client library of choice to support both sync and async calls.

```php
$graphClient = new Graph(NationalCloud::GLOBAL); // creates & uses a default Guzzle client under the hood
```

#### 1. Custom configure a Guzzle client using the `HttpClientFactory`
------------------------------------------------------------------------
To configure a Guzzle client to use with the SDK
```php
$config = []; // your desired Guzzle client config
$httpClient = HttpClientFactory::setClientConfig($config)::createAdapter();
$graphClient = new Graph(NationalCloud::GLOBAL, $httpClient);
```

If you'd like to use the raw Guzzle client directly
```php
$config = [
// custom request options
];
$guzzleClient = HttpClientFactory::setClientConfig($config)::create();
$response = $guzzleClient->get("/users/me");
```

We would have loved to allow you to pass your guzzle client directly to the HttpClientFactory, however we would not be able to attach our recommended configs since Guzzle's `getConfig()` method is set to be deprecated in Guzzle 8.


#### 2. Configure any other HTTP client
----------------------------------------
Implement the `Microsoft\Graph\Http\HttpClientInterface` and pass your implementation to the `Graph` constructor.

#### 3. Overwrite the HTTP client while making synchronous requests
--------------------------------------------------------------------
The SDK supports use of any PSR-18 compliant client for synchronous requests.

```php
$customPsr18Client = new Psr18Client();
$graphClient = new Graph();
$response = $graphClient->setAccessToken("abc)
->createRequest("GET", "/user/id")
->execute($customPsr18Client); // overwrites the default Guzzle client created by Graph()
```

#### 4. Overwrite the HTTP client while making asynchronous requests
----------------------------------------------------------------------
The SDK supports using any HTTPlug HttpAsyncClient implementation for asynchronous requests
```php
$customClient = new HttpAsyncClientImpl();
$graphClient = new Graph();
$response = $graphClient->setAccessToken("abc")
->createRequest("GET", "/user/id")
->executeAsync($customClient); // overwrite the default Guzzle client created by Graph()
```


### Introducing the `GraphClientException`
This will be the exception type thrown going forward with regard to Graph client and GraphRequest configuration issues.


### Introducing the `GraphServiceException`
This is the new standard exception to be thrown for `4xx` and `5xx` responses from the Graph. The exception contains the error payload returned by the Graph API via `getError()`.

### `GraphRequest` changes

#### 1. Deprecated functionality
---------------------------------
- Deprecates Guzzle-specific config and methods. We recommend using `HttpClientFactory` to configure your client:
- Deprecated `setHttpErrors()`, `setTimeout()`.
- Deprecated `proxyPort` and `proxyVerifySSL`.
- `$headers` and `$requestBody` are no longer `protected` attributes. Now `private`.
- Deprecates some getters: `getBaseUrl()`, `getApiVersion()`, `getReturnsStream()`.


#### 2. Setting return type
----------------------------
- `setReturnType()` throws a `GraphClientException` if the return class passed is invalid.
- Deprecates setting return type to `GuzzleHttp\Psr7\Stream` in favour of `Psr\Http\Message\StreamInterface` to get a stream returned. This is because of our efforts
to make the SDK PSR compliant.


#### 3. Headers
----------------
- `getHeaders()` now returns `array<string, string[]` instead of previous `array<string, string>`.
- `addHeaders()` also supports passing `array<string, string|string[]>`.
- `addHeaders()` throws a `GraphClientException` if you attempt to overwrite the SDK Version header.
- Extra layer of security by preventing sending your authorization tokens to non-Graph endpoints.

#### 4. Request Body
----------------------
- Supports passing any PSR-7 `StreamInterface` implementation to `attachBody()`.


#### 5. Making Requests
-------------------------
- `execute()`, `download()` and `upload()` all accept any PSR-18 `ClientInterface` implementation to overwrite the SDK's default Guzzle client.
- `execute()` now throws PSR-18 `ClientExceptionInterface` as opposed to `\GuzzleHttp\Exception\GuzzleException`.
- `executeAsync()` now returns a HTTPlug `Http\Promise\Promise` instead of the previous Guzzle `GuzzleHttp\Promise\PromiseInterface`.
- `executeAsync()` fails with a PSR-18 `ClientExceptionInterface` for HTTP client issues or `GraphServiceException` for 4xx/5xx response.
- `download()` throws a `Psr\Http\Client\ClientExceptionInterface` as opposed to the previous `GuzzleHttp\Exception\GuzzleException`.
- `download()` and `upload()` now throw a `GraphClientException` if the SDK is unable to open the file path given and read/write to it.


#### 6. Handling responses
----------------------------
- For `4xx` and `5xx` responses, the SDK will throw a `GraphServiceException` which contains the error payload via `getError()`.
- The status code is now an `int` from the previous `string` i.e. `getStatus()`.

### `GraphCollectionRequest` changes
- Executing `count()` requests now throws PSR-18 `ClientExceptionInterface` in case of any HTTP related issues & a `GraphClientException` if the `@odata.count` does not exist in the payload.
- `setPageSize()` throws a `GraphClientException` from the previous `GraphException`.
- `getPage()` throws `Psr\Http\Client\ClientExceptionInterface` from previous `GuzzleHttp\Exception\GuzzleException`.
- `getPage()` has been aligned to `execute()` to return a `GraphResponse` object if no return type is specified from previous JSON-decoded payload array.
You can call `getBody()` on the `GraphResponse` returned to get the JSON-decoded array. If a return type is specified, `getPage()`
still returns the deserialized response body.
- makes `setPageCallInfo()` and `processPageCallReturn()` `private` as these methods provide low level implementation detail.
- See `GraphRequest` changes above as well.

### Introducing the `PageIterator`
- The `PageIterator` allows you to now asynchronously process each entity in a paged collection response without having to fetch each page synchronously via `getPage()`.
- A callback function that processes each entity is required. Note that the type of the object passed to your callback is matches the return type set on the request.
If no return type is specified, a JSON-decoded entity array will be passed to your callback.
- You control when to pause processing and resume from the last entity processed using the callback's return value. If the callback returns `true`, iteration continues. If it returns `false` the iterator pauses.
- Calling `resume()` continues iteration from the next entity in the collection.
- The `PageIterator` returns a promise which resolves to `true` and throws exceptions should any be encountered.
- Should your access token expire during iteration you can `setAccessToken()` then `resume()`.

```php
$callback = function (\Microsoft\Graph\Model\Message $message) {
// your logic
};
$iterator = $graphClient->createCollectionRequest("GET", "/me/messages")
->setReturnType(\Microsoft\Graph\Model\Message::class)
->pageIterator($callback);
$promise = $iterator->iterate();

# Resuming iteration
$promise = $iterator->resume();

# Setting a new access token in case it expires during iteration
$promise = $iterator->setAccessToken("abc")->resume();
```

7 changes: 3 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.0 || ^9.0",
"phpunit/phpunit": "^9.0",
"mikey179/vfsstream": "^1.2",
"phpstan/phpstan": "^0.12.90"
},
Expand All @@ -31,8 +31,7 @@
},
"autoload-dev": {
"psr-4": {
"Microsoft\\Graph\\Test\\": "tests/",
"Microsoft\\Graph\\Http\\Test\\": "tests/Http/"
"Microsoft\\Graph\\Test\\": "tests/"
}
}
}
}
15 changes: 9 additions & 6 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<coverage includeUncoveredFiles="true"
pathCoverage="true">
<include>
<directory suffix=".php">src</directory>
<exclude><directory suffix=".php">src/Model</directory></exclude>
</whitelist>
</filter>
</phpunit>
</include>
<report>
<html outputDirectory="coverage"/>
</report>
</coverage>
</phpunit>
3 changes: 3 additions & 0 deletions src/Core/GraphConstants.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

final class GraphConstants
{
const BETA_API_VERSION = "beta";
const V1_API_VERSION = "v1.0";

// These can be overwritten in setters in the Graph object
const REST_ENDPOINT = "https://graph.microsoft.com/";

Expand Down
22 changes: 16 additions & 6 deletions src/Core/NationalCloud.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,23 @@ final class NationalCloud
* @return bool
*/
public static function containsNationalCloudHost(string $url): bool {
self::initHosts();
$validUrlParts = parse_url($url);
return $validUrlParts
&& array_key_exists("scheme", $validUrlParts)
&& $validUrlParts["scheme"] == "https"
&& array_key_exists("host", $validUrlParts)
&& array_key_exists(strtolower($validUrlParts["host"]), self::$hosts);
return self::containsNationalCloudHostFromUrlParts($validUrlParts);
}

/**
* Checks if $urlParts contain a valid National Cloud host
*
* @param array<string, string>|false $urlParts return value of parse_url()
* @return bool
*/
public static function containsNationalCloudHostFromUrlParts($urlParts): bool {
self::initHosts();
return $urlParts
&& array_key_exists("scheme", $urlParts)
&& $urlParts["scheme"] == "https"
&& array_key_exists("host", $urlParts)
&& array_key_exists(strtolower($urlParts["host"]), self::$hosts);
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/Exception/GraphClientException.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
namespace Microsoft\Graph\Exception;

/**
* Class ClientInitialisationException
*
* Class GraphClientException
* @package Microsoft\Graph\Exception
* @copyright 2021 Microsoft Corporation
* @license https://opensource.org/licenses/MIT MIT License
Expand Down
Loading