Skip to content

Commit

Permalink
Merge pull request #101 from microsoft/dev
Browse files Browse the repository at this point in the history
Release 0.9.0
  • Loading branch information
Ndiritu authored Oct 30, 2023
2 parents 2ce8493 + b5c8874 commit ef1f830
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 39 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

## [0.8.5]
## [0.9.0] - 2023-10-30

### Added
- Adds generic types to Promise types in PHPDocs

### Changed
- Ensure changes to nested BackedModel or array<BackedModel> properties in the backing store makes the entire backing store value dirty.

## [0.8.5] - 2023-10-17

### Changed
- Disabled auto-suggestion of PSR implementations by OpenTelemetry SDK. [#95](https://github.com/microsoft/kiota-abstractions-php/pull/95)
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
},
"require": {
"php": "^7.4 || ^8.0",
"php-http/promise": "^1.1.0",
"php-http/promise": "^1.2.0",
"doctrine/annotations": "^1.13 || ^2.0",
"open-telemetry/sdk": "^0.0.17",
"open-telemetry/api": "^0.0.17",
"ramsey/uuid": "^3 || ^4",
"stduritemplate/stduritemplate": "^0.0.43",
"stduritemplate/stduritemplate": "^0.0.44 || ^0.0.46",
"psr/http-message": "^1.1 || ^2.0"
},
"require-dev": {
Expand Down
2 changes: 1 addition & 1 deletion src/Authentication/AccessTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface AccessTokenProvider
*
* @param string $url the target URI to get an access token for
* @param array<string, mixed> $additionalAuthenticationContext
* @return Promise
* @return Promise<string>
*/
public function getAuthorizationTokenAsync(string $url, array $additionalAuthenticationContext = []): Promise;

Expand Down
4 changes: 2 additions & 2 deletions src/Authentication/AnonymousAuthenticationProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ class AnonymousAuthenticationProvider implements AuthenticationProvider {
/**
* @param RequestInformation $request Request information
* @param array<string, mixed> $additionalAuthenticationContext
* @return Promise
* @return Promise<RequestInformation>
*/
public function authenticateRequest(
RequestInformation $request,
array $additionalAuthenticationContext = []
): Promise
{
return new FulfilledPromise(null);
return new FulfilledPromise($request);
}
}
2 changes: 1 addition & 1 deletion src/Authentication/AuthenticationProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface AuthenticationProvider {
/**
* @param RequestInformation $request
* @param array<string, mixed> $additionalAuthenticationContext
* @return Promise
* @return Promise<RequestInformation>
*/
public function authenticateRequest(
RequestInformation $request,
Expand Down
7 changes: 3 additions & 4 deletions src/Authentication/BaseBearerTokenAuthenticationProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Http\Promise\FulfilledPromise;
use Http\Promise\Promise;
use League\Uri\Contracts\UriException;
use Microsoft\Kiota\Abstractions\RequestInformation;

/**
Expand Down Expand Up @@ -58,7 +57,7 @@ public function getAccessTokenProvider(): AccessTokenProvider
/**
* @param RequestInformation $request
* @param array<string, mixed> $additionalAuthenticationContext
* @return Promise
* @return Promise<RequestInformation>
*/
public function authenticateRequest(
RequestInformation $request,
Expand All @@ -78,9 +77,9 @@ public function authenticateRequest(
if ($token) {
$request->addHeader(self::$authorizationHeaderKey, "Bearer {$token}");
}
return null;
return $request;
});
}
return new FulfilledPromise(null);
return new FulfilledPromise($request);
}
}
2 changes: 1 addition & 1 deletion src/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

final class Constants
{
public const VERSION = '0.8.5';
public const VERSION = '0.9.0';
}
21 changes: 19 additions & 2 deletions src/NativeResponseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,32 @@
*/
class NativeResponseHandler implements ResponseHandler
{

/**
* @var ResponseInterface|null
*/
private ?ResponseInterface $nativeResponse = null;

/**
* Returns the PSR-7 Response
*
* @return ResponseInterface|null
*/
public function getResponse(): ?ResponseInterface
{
return $this->nativeResponse;
}

/**
* Returns a promise that resolves to the raw PSR-7 response
*
* @param ResponseInterface $response
* @param array<string, array{string, string}>|null $errorMappings
* @return Promise
* @return Promise<null>
*/
public function handleResponseAsync(ResponseInterface $response, ?array $errorMappings = null): Promise
{
return new FulfilledPromise($response);
$this->nativeResponse = $response;
return new FulfilledPromise(null);
}
}
25 changes: 16 additions & 9 deletions src/RequestAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@
use Microsoft\Kiota\Abstractions\Serialization\SerializationWriterFactory;
use Microsoft\Kiota\Abstractions\Store\BackingStoreFactory;
use Microsoft\Kiota\Abstractions\Serialization\Parsable;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/** Service responsible for translating abstract Request Info into concrete native HTTP requests. */
/**
* Service responsible for translating abstract Request Info into concrete native HTTP requests.
*/
interface RequestAdapter {
/**
* Executes the HTTP request specified by the given RequestInformation and returns the deserialized response model.
* @template T of Parsable
* @template V of Parsable
* @param RequestInformation $requestInfo the request info to execute.
* @param array{class-string<T>,string} $targetCallable the model to deserialize the response into.
* @param array<string, array{class-string<T>, string}>|null $errorMappings
* @return Promise with the deserialized response model.
* @param array<string, array{class-string<V>, string}>|null $errorMappings
* @return Promise<T|null> with the deserialized response model.
*/
public function sendAsync(
RequestInformation $requestInfo,
Expand All @@ -39,10 +45,11 @@ public function getParseNodeFactory(): ParseNodeFactory;
/**
* Executes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection.
* @template T of Parsable
* @template V of Parsable
* @param RequestInformation $requestInfo the request info to execute.
* @param array{class-string<T>,string} $targetCallable the callable representing object creation logic.
* @param array<string, array{class-string<T>, string}>|null $errorMappings
* @return Promise with the deserialized response model collection.
* @param array<string, array{class-string<V>, string}>|null $errorMappings
* @return Promise<array<T|null>|null> with the deserialized response model collection.
*/
public function sendCollectionAsync(
RequestInformation $requestInfo,
Expand All @@ -56,7 +63,7 @@ public function sendCollectionAsync(
* @param RequestInformation $requestInfo
* @param string $primitiveType e.g. int, bool
* @param array<string, array{class-string<T>, string}>|null $errorMappings
* @return Promise
* @return Promise<mixed|StreamInterface>
*/
public function sendPrimitiveAsync(
RequestInformation $requestInfo,
Expand All @@ -70,7 +77,7 @@ public function sendPrimitiveAsync(
* @param RequestInformation $requestInfo
* @param string $primitiveType e.g. int, bool
* @param array<string, array{class-string<T>, string}>|null $errorMappings
* @return Promise
* @return Promise<array<mixed>|null>
*/
public function sendPrimitiveCollectionAsync(
RequestInformation $requestInfo,
Expand All @@ -83,7 +90,7 @@ public function sendPrimitiveCollectionAsync(
* @template T of Parsable
* @param RequestInformation $requestInfo
* @param array<string, array{class-string<T>, string}>|null $errorMappings
* @return Promise
* @return Promise<null>
*/
public function sendNoContentAsync(RequestInformation $requestInfo, ?array $errorMappings = null): Promise;
/**
Expand All @@ -108,7 +115,7 @@ public function getBaseUrl(): string;
* Converts RequestInformation object to an authenticated(containing auth header) PSR-7 Request Object.
*
* @param RequestInformation $requestInformation
* @return Promise
* @return Promise<RequestInterface>
*/
public function convertToNative(RequestInformation $requestInformation): Promise;
}
8 changes: 5 additions & 3 deletions src/ResponseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@

use Http\Promise\Promise;
use Psr\Http\Message\ResponseInterface;
use Microsoft\Kiota\Abstractions\Serialization\Parsable;

interface ResponseHandler {
/**
* Callback method that is invoked when a response is received.
* @template T of Parsable
* @param ResponseInterface $response The native response object.
* @param array<string, array<string, string>>|null $errorMappings map of error status codes to exception models
* to deserialize to
* @return Promise A Promise that represents the asynchronous operation and contains the deserialized response.
* @param array<string, array{class-string<T>, string}>|null $errorMappings
* map of error status codes to exception models to deserialize to
* @return Promise<mixed> A Promise that contains the deserialized response.
*/
public function handleResponseAsync(ResponseInterface $response, ?array $errorMappings = null): Promise;
}
57 changes: 51 additions & 6 deletions src/Store/InMemoryBackingStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,27 @@ public function get(string $key) {
public function set(string $key, $value): void
{
$oldValue = $this->store[$key] ?? null;
$valueToAdd = is_array($value) ? [$this->isInitializationCompleted, $value, count($value)] : [$this->isInitializationCompleted, $value];
$valueToAdd = is_array($value) ?
[$this->isInitializationCompleted, $value, count($value)] : [$this->isInitializationCompleted, $value];

// Dirty track changes if $value is a model and its properties change
if (!array_key_exists($key, $this->store)) {
if ($value instanceof BackedModel && $value->getBackingStore()) {
$value->getBackingStore()->subscribe(fn ($propertyKey, $oldVal, $newVal) => $this->set($key, $value));
$value->getBackingStore()->subscribe(function ($propertyKey, $oldVal, $newVal) use ($key, $value) {
// Mark all properties as dirty
$value->getBackingStore()->setIsInitializationCompleted(false);
$this->set($key, $value);
});
}
if (is_array($value)) {
array_map(function ($item) use ($key, $value) {
if ($item instanceof BackedModel && $item->getBackingStore()) {
$item->getBackingStore()->subscribe(fn ($propertyKey, $oldVal, $newVal) => $this->set($key, $value));
$item->getBackingStore()->subscribe
(function ($propertyKey, $oldVal, $newVal) use ($key, $value, $item) {
// Mark all properties as dirty
$item->getBackingStore()->setIsInitializationCompleted(false);
$this->set($key, $value);
});
}
}, $value);
}
Expand Down Expand Up @@ -113,6 +123,20 @@ public function clear(): void {
*/
public function setIsInitializationCompleted(bool $value): void {
$this->isInitializationCompleted = $value;
// Update existing values in store
foreach ($this->store as $key => $storedValue) {
$storedValue[0] = !$storedValue[0];
if ($storedValue[1] instanceof BackedModel && $storedValue[1]->getBackingStore()) {
$storedValue[1]->getBackingStore()->setIsInitializationCompleted($value);
}
if (is_array($storedValue[1])) {
array_map(function ($item) use ($value) {
if ($item instanceof BackedModel && $item->getBackingStore()) {
$item->getBackingStore()->setIsInitializationCompleted($value);
}
}, $storedValue[1]);
}
}
}

/**
Expand Down Expand Up @@ -174,9 +198,30 @@ private function getValueFromWrapper(array $wrapper) {
*/
private function checkCollectionSizeChanged(string $key): void {
$wrapper = $this->store[$key] ?? null;
if ($wrapper && is_array($wrapper[1])) {
if (sizeof($wrapper[1]) != $wrapper[2]) {
$this->set($key, $wrapper[1]);
if ($wrapper) {
if (is_array($wrapper[1])) {
array_map(function ($item) {
if ($item instanceof BackedModel && $item->getBackingStore()) {
// Call get() on nested properties so that this method may be called recursively
// to ensure collections are consistent
array_map(
fn ($itemKey) => $item->getBackingStore()->get($itemKey),
array_keys($item->getBackingStore()->enumerate())
);
}
}, $wrapper[1]);

if (sizeof($wrapper[1]) != $wrapper[2]) {
$this->set($key, $wrapper[1]);
}
}
if ($wrapper[1] instanceof BackedModel && $wrapper[1]->getBackingStore()) {
// Call get() on nested properties so that this method may be called recursively
// to ensure collections are consistent
array_map(
fn ($itemKey) => $wrapper[1]->getBackingStore()->get($itemKey),
array_keys($wrapper[1]->getBackingStore()->enumerate())
);
}
}
}
Expand Down
48 changes: 41 additions & 7 deletions tests/Store/InMemoryBackingStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,33 +149,67 @@ public function testChangesToBackedModelCollection(): void
$this->assertEquals(2, sizeof($this->backingStore->get('key')));
$this->assertEquals(250, $this->backingStore->get('key')[1]->getAge());
}


public function testChangesToBackedModelAsBackingStoreValueMakesEntireModelDirty(): void
{
$nestedBackedModel = new SampleBackedModel(10, 'name');

$this->backingStore->set('user', $nestedBackedModel);
$this->backingStore->setIsInitializationCompleted(true);

$nestedBackedModel->setAge(5);

$this->backingStore->setReturnOnlyChangedValues(true);
$nestedBackedModel->getBackingStore()->setReturnOnlyChangedValues(true);

$this->assertInstanceOf(BackedModel::class, $this->backingStore->get('user'));
$this->assertEquals(2, sizeof(array_keys($this->backingStore->get('user')->getBackingStore()->enumerate())));
}

public function testChangesToBackedModelCollectionAsBackingStoreValueMakesEntireModelDirty(): void
{
$nestedBackedModel = new SampleBackedModel(10, 'name');
$anotherNestedBackedModel = new SampleBackedModel(100, 'FirstName');

$this->backingStore->set('user', [$nestedBackedModel, $anotherNestedBackedModel]);
$this->backingStore->setIsInitializationCompleted(true);

$nestedBackedModel->setAge(5);

$this->backingStore->setReturnOnlyChangedValues(true);
$nestedBackedModel->getBackingStore()->setReturnOnlyChangedValues(true);

$this->assertIsArray($this->backingStore->get('user'));
$this->assertEquals(2, sizeof($this->backingStore->get('user')));
$this->assertEquals(2, sizeof(array_keys($this->backingStore->get('user')[0]->getBackingStore()->enumerate())));
$this->assertEquals(2, sizeof(array_keys($this->backingStore->get('user')[1]->getBackingStore()->enumerate())));
}
}

class SampleBackedModel implements BackedModel
{
private int $age;
private ?string $name = null;
private BackingStore $backingStore;

public function __construct(int $age, ?string $name = null)
public function __construct(?int $age = null, ?string $name = null)
{
$this->backingStore = BackingStoreFactorySingleton::getInstance()->createBackingStore();
$this->setAge($age);
$this->setName($name);
}

/**
* @return int
* @return int|null
*/
public function getAge(): int
public function getAge(): ?int
{
return $this->backingStore->get("age");
}

/**
* @param int $age
* @param int|null $age
*/
public function setAge(int $age): void
public function setAge(?int $age): void
{
$this->backingStore->set("age", $age);
}
Expand Down

0 comments on commit ef1f830

Please sign in to comment.