Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 59 additions & 0 deletions daprdocs/content/en/php-sdk-docs/php-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
type: docs
title: "Configuration with PHP"
linkTitle: "Configuration"
weight: 1000
description: How to read configuration items and subscribe to configuration changes
no_list: true
---

The [Configuration API]({{% ref configuration-api-overview %}}) lets you read configuration items from a
configuration store and subscribe to changes.

## Getting configuration items

Create a client and read items by key. Omit the keys to retrieve all items in the store:

```php
<?php

$client = \Dapr\Client\DaprClient::clientBuilder()->build();

$items = $client->getConfiguration(storeName: 'configstore', keys: ['orderId1', 'orderId2']);

foreach ($items as $key => $item) {
// $item is a \Dapr\Configuration\ConfigurationItem
echo "$key = {$item->value} (version: {$item->version})";
}
```

## Subscribing to changes

Subscribing returns a subscription id. The sidecar pushes updates to your application's
`/configuration/<store-name>` route:

```php
$id = $client->subscribeConfiguration(storeName: 'configstore', keys: ['orderId1', 'orderId2']);

// when you no longer need updates
$client->unsubscribeConfiguration(storeName: 'configstore', id: $id);
```

Receive the pushed updates in an app route, parsing the body with
`\Dapr\Configuration\ConfigurationUpdate`:

```php
<?php

$app = \Dapr\App::create();
$app->post('/configuration/configstore', function (#[\Dapr\Attributes\FromBody] string $body) {
$update = \Dapr\Configuration\ConfigurationUpdate::parse($body);
foreach ($update->items as $key => $item) {
echo "config changed: $key = {$item->value}";
}
});
$app->start();
```

Every client method also has an `*Async` variant (for example, `getConfigurationAsync()`) that returns a
Guzzle promise.
23 changes: 23 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,29 @@ $client->getSecret(storeName: 'kubernetes', key: 'test');
$client->getBulkSecret(storeName: 'kubernetes');
```

# Accessing Configuration

You can read configuration items from a configuration store and subscribe to changes:

```php
<?php

// get specific configuration items (omit keys to get all items)
$items = $client->getConfiguration(storeName: 'configstore', keys: ['orderId1', 'orderId2']);
foreach ($items as $key => $item) {
// $item is a \Dapr\Configuration\ConfigurationItem with ->value, ->version, ->metadata
}

// subscribe to changes; returns a subscription id. The sidecar pushes updates to your
// app's `/configuration/<store-name>` route.
$id = $client->subscribeConfiguration(storeName: 'configstore', keys: ['orderId1']);

// later, unsubscribe
$client->unsubscribeConfiguration(storeName: 'configstore', id: $id);
```

Parse pushed updates in your app route with `\Dapr\Configuration\ConfigurationUpdate::parse($json)`.

# Accessing State

There are several ways to access state. You can access state directly via the client or abstract access via an object.
Expand Down
68 changes: 68 additions & 0 deletions src/lib/Client/DaprClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Dapr\Actors\IActorReference;
use Dapr\Actors\Reminder;
use Dapr\Actors\Timer;
use Dapr\Configuration\ConfigurationItem;
use Dapr\consistency\Consistency;
use Dapr\Deserialization\DeserializationConfig;
use Dapr\Deserialization\IDeserializer;
Expand Down Expand Up @@ -444,6 +445,73 @@ abstract public function getBulkSecretAsync(string $storeName, array $metadata =
*/
abstract public function getBulkSecret(string $storeName, array $metadata = []): array;

/**
* Get configuration items from a configuration store.
*
* @param string $storeName The name of the configuration store
* @param array<array-key, string> $keys The keys of the configuration items to get; all items if empty
* @param array<array-key, string> $metadata Optional metadata passed to the configuration store
*
* @return array<string, ConfigurationItem> The configuration items, keyed by their configuration key
*/
abstract public function getConfiguration(string $storeName, array $keys = [], array $metadata = []): array;

/**
* @param string $storeName
* @param array<array-key, string> $keys
* @param array<array-key, string> $metadata
*
* @return PromiseInterface<array<string, ConfigurationItem>>
*/
abstract public function getConfigurationAsync(
string $storeName,
array $keys = [],
array $metadata = []
): PromiseInterface;

/**
* Subscribe to configuration changes in a configuration store. The sidecar pushes updates to the
* application's `/configuration/<store-name>` route; parse them with ConfigurationUpdate::parse().
*
* @param string $storeName The name of the configuration store
* @param array<array-key, string> $keys The keys to subscribe to; all keys if empty
* @param array<array-key, string> $metadata Optional metadata passed to the configuration store
*
* @return string The subscription id, used to unsubscribe
*/
abstract public function subscribeConfiguration(string $storeName, array $keys = [], array $metadata = []): string;

/**
* @param string $storeName
* @param array<array-key, string> $keys
* @param array<array-key, string> $metadata
*
* @return PromiseInterface<string>
*/
abstract public function subscribeConfigurationAsync(
string $storeName,
array $keys = [],
array $metadata = []
): PromiseInterface;

/**
* Unsubscribe from configuration changes.
*
* @param string $storeName The name of the configuration store
* @param string $id The subscription id returned by subscribeConfiguration()
*
* @return bool True if the unsubscription succeeded
*/
abstract public function unsubscribeConfiguration(string $storeName, string $id): bool;

/**
* @param string $storeName
* @param string $id
*
* @return PromiseInterface<bool>
*/
abstract public function unsubscribeConfigurationAsync(string $storeName, string $id): PromiseInterface;

/**
* Check if the daprd instance is up and running.
*
Expand Down
1 change: 1 addition & 0 deletions src/lib/Client/DaprHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class DaprHttpClient extends DaprClient
{
use HttpStateTrait;
use HttpSecretsTrait;
use HttpConfigurationTrait;
use HttpInvokeTrait;
use HttpPubSubTrait;
use HttpBindingTrait;
Expand Down
97 changes: 97 additions & 0 deletions src/lib/Client/HttpConfigurationTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace Dapr\Client;

use Dapr\Configuration\ConfigurationItem;
use Dapr\Deserialization\IDeserializer;
use GuzzleHttp\Client;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;

/**
* Trait HttpConfigurationTrait
* @package Dapr\Client
*/
trait HttpConfigurationTrait
{
use PromiseHandlingTrait;

public IDeserializer $deserializer;
protected Client $httpClient;

/**
* @return array<string, ConfigurationItem>
*/
public function getConfiguration(string $storeName, array $keys = [], array $metadata = []): array
{
return $this->getConfigurationAsync($storeName, $keys, $metadata)->wait();
}

/**
* @return PromiseInterface<array<string, ConfigurationItem>>
*/
public function getConfigurationAsync(string $storeName, array $keys = [], array $metadata = []): PromiseInterface
{
$storeName = rawurlencode($storeName);
$options = $this->buildConfigurationOptions($keys, $metadata);
return $this->handlePromise(
$this->httpClient->getAsync("/v1.0/configuration/$storeName", $options),
fn(ResponseInterface $response) => $this->deserializer->from_array_of(
ConfigurationItem::class,
json_decode($response->getBody()->getContents(), true) ?? []
)
);
}

public function subscribeConfiguration(string $storeName, array $keys = [], array $metadata = []): string
{
return $this->subscribeConfigurationAsync($storeName, $keys, $metadata)->wait();
}

public function subscribeConfigurationAsync(
string $storeName,
array $keys = [],
array $metadata = []
): PromiseInterface {
$storeName = rawurlencode($storeName);
$options = $this->buildConfigurationOptions($keys, $metadata);
return $this->handlePromise(
$this->httpClient->getAsync("/v1.0/configuration/$storeName/subscribe", $options),
fn(ResponseInterface $response) => json_decode($response->getBody()->getContents(), true)['id']
);
}

public function unsubscribeConfiguration(string $storeName, string $id): bool
{
return $this->unsubscribeConfigurationAsync($storeName, $id)->wait();
}

public function unsubscribeConfigurationAsync(string $storeName, string $id): PromiseInterface
{
$storeName = rawurlencode($storeName);
$id = rawurlencode($id);
return $this->handlePromise(
$this->httpClient->getAsync("/v1.0/configuration/$storeName/$id/unsubscribe"),
fn(ResponseInterface $response) => json_decode($response->getBody()->getContents(), true)['ok'] ?? false
);
}

/**
* Build the guzzle options for a configuration request. Keys are sent as repeated
* `key=` query parameters, which Dapr expects, so the query string is built manually.
*
* @param array<array-key, string> $keys
* @param array<array-key, string> $metadata
*
* @return array
*/
private function buildConfigurationOptions(array $keys, array $metadata): array
{
$params = array_map(fn($key) => 'key=' . rawurlencode($key), $keys);
foreach ($metadata as $key => $value) {
$params[] = 'metadata.' . rawurlencode($key) . '=' . rawurlencode($value);
}

return empty($params) ? [] : ['query' => implode('&', $params)];
}
}
32 changes: 32 additions & 0 deletions src/lib/Configuration/ConfigurationItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Dapr\Configuration;

/**
* Class ConfigurationItem
*
* A single configuration item as returned by a configuration store.
*
* Note: properties use plain defaults (rather than constructor promotion) because the
* deserializer instantiates this class without calling the constructor, and stores may
* omit keys such as version or metadata.
*
* @package Dapr\Configuration
*/
class ConfigurationItem
{
/**
* @var string The value of the configuration item
*/
public string $value = '';

/**
* @var string The version of the configuration item, if the store supports versions
*/
public string $version = '';

/**
* @var array|null Optional metadata associated with the configuration item
*/
public array|null $metadata = null;
}
54 changes: 54 additions & 0 deletions src/lib/Configuration/ConfigurationUpdate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Dapr\Configuration;

/**
* Class ConfigurationUpdate
*
* Represents a configuration change notification pushed by the sidecar to the
* application's `/configuration/<store-name>` route after subscribing via
* DaprClient::subscribeConfiguration().
*
* @package Dapr\Configuration
*/
class ConfigurationUpdate
{
/**
* ConfigurationUpdate constructor.
*
* @param string $id The subscription id this update belongs to
* @param ConfigurationItem[] $items The updated configuration items, keyed by their configuration key
*/
public function __construct(
public string $id = '',
public array $items = []
) {
}

/**
* Parse the raw JSON body of a configuration update delivered by the sidecar.
*
* @param string $json The raw JSON body
*
* @return ConfigurationUpdate The parsed update
*/
public static function parse(string $json): ConfigurationUpdate
{
$raw = json_decode($json, true) ?? [];
$update = new ConfigurationUpdate();
$update->id = $raw['id'] ?? '';
$update->items = array_map(
function (array $item) {
$configItem = new ConfigurationItem();
$configItem->value = $item['value'] ?? '';
$configItem->version = $item['version'] ?? '';
$configItem->metadata = $item['metadata'] ?? null;

return $configItem;
},
$raw['items'] ?? []
);

return $update;
}
}
Loading