Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/dapr/php-sdk
Dapr SDK for PHP
https://github.com/dapr/php-sdk
Last synced: about 22 hours ago
JSON representation
Dapr SDK for PHP
- Host: GitHub
- URL: https://github.com/dapr/php-sdk
- Owner: dapr
- License: apache-2.0
- Created: 2020-12-15T20:05:12.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2024-05-17T12:21:43.000Z (6 months ago)
- Last Synced: 2024-09-19T04:45:41.292Z (about 2 months ago)
- Language: PHP
- Size: 830 KB
- Stars: 70
- Watchers: 11
- Forks: 15
- Open Issues: 20
-
Metadata Files:
- Readme: readme.md
- License: LICENSE
Awesome Lists containing this project
- awesome-dapr - Dapr PHP SDK - Dapr SDK for PHP (Uncategorized / Uncategorized)
README
# Using this library
[![PHP Composer](https://github.com/dapr/php-sdk/actions/workflows/php.yml/badge.svg)](https://github.com/dapr/php-sdk/actions/workflows/php.yml)
This library is licensed with the MIT license.
Add the library to your `composer.json`:
> composer require dapr/php-sdk
Some basic documentation is below, more documentation can be
found [in the docs](https://docs.dapr.io/developing-applications/sdks/php/);# Migrating to 1.2
In preparation for gRPC support in this SDK, there's now a new DaprClient in `\Dapr\Client\DaprClient`. Please update
your code to use the new client.There shouldn't be many changes to your code to upgrade to 1.2+ from a prior version. Namely, there are some
deprecations:## Deprecations
The following have been deprecated and will be removed in 1.4+.
### \Dapr\SecretManager has been deprecated
Simply use the new client instead.
### \Dapr\Client has been deprecated
Simply use the new client: `\Dapr\Client\DaprClient`.
### \Dapr\PubSub\Publish has been deprecated
Simply instantiate `\Dapr\PubSub\Topic` directly or use the new client directly.
## Fallbacks and Upgrades
### \Dapr\State\StateManager
This class has been upgraded to use the new client. It shouldn't require any changes to your code, however, the old
behavior can be utilized with `\Dapr\State\StateManagerOld`.### \Dapr\State\TransactionalState
This class has been upgrade to use the new client. It shouldn't require any changes to your code, however, the old
behavior can be utilized with `\Dapr\State\TransactionalStateOld`.# Creating a Dapr Client
```php
$client = \Dapr\Client\DaprClient::clientBuilder()->build();
```## Using the Middleware
The `App` object also implements a PSR-15 compatible middleware which implements the actor routes and subscribe routes
for you.```php
$app = \Dapr\App::create(configure: [
// custom configuration
]);
use_middleware($app);
```# Accessing Secrets
You can access secrets easily:
```php
getSecret(storeName: 'kubernetes', key: 'test');// retrieve all secrets
$client->getBulkSecret(storeName: 'kubernetes');
```# Accessing State
There are several ways to access state. You can access state directly via the client or abstract access via an object.
## Accessing State Directly
```php
['value' => $value, 'etag' => $etag] = $client->getStateAndEtag(storeName: 'statestore', key: 'key', asType: SomeClass::class, consistency: \Dapr\consistency\EventualLastWrite::instance());
$value = $client->getState(storeName: 'statestore', key: 'key', asType: 'string',consistency: \Dapr\consistency\StrongFirstWrite::instance())
```## Abstract via Object
```php
counter += $amount;
}
}$app = \Dapr\App::create();
$app->post('/my-state/{key}', function (
string $key,
#[\Dapr\Attributes\FromBody] string $body,
\Dapr\State\StateManager $stateManager) {
$stateManager->save_state(store_name: 'store', item: new \Dapr\State\StateItem(key: $key, value: $body));
$stateManager->save_object(new MyState);
});
$app->get('/my-state/{key}', function(string $key, \Dapr\State\StateManager $stateManager) {
return $stateManager->load_state(store_name: 'store', key: $key);
});
$app->start();
```## Transactional State
You can also use transactional state to interact with state objects by extending `TransactionalState` with our state
objects or commit transactions directly.### Directly with the client
```php
$transaction = [
\Dapr\Client\StateTransactionRequest::upsert(key: 'key', value: $client->serializer->as_json($new_value)),
\Dapr\Client\StateTransactionRequest::delete(key: 'key');
];
$client->executeStateTransaction(storeName: 'statestore', operations: $transaction);
```### Abstracted via an Object
```php
#[\Dapr\State\Attributes\StateStore('statestore', \Dapr\consistency\StrongFirstWrite::class)]
class SomeState extends \Dapr\State\TransactionalState {
public string $value;
public function ok(): void {
$this->value = 'ok';
}
}$app = Dapr\App::create();
$app->get('/do-work', function(SomeState $state) {
$state->begin();
$state->value = 'not-ok';
$state->ok();
$state->commit();
return $state;
});
$app->start();
```# Actors
Actors are fully implemented and quite powerful. In order to define an actor, you must first define the interface.
You'll likely want to put this in a separate library for easy calling from other services.```php
state->count += $amount;
}
}// register the actor with the runtime
$app = \Dapr\App::create(configure: fn(\DI\ContainerBuilder $builder) => $builder->addDefinitions([
'dapr.actors' => [Counter::class]
]));
$app->start();
```The state to inject is read from the constructor arguments, the state must derive from `ActorState` to be injected. You
may use as many state classes as you'd like. State is automatically saved for you if you make any changes to it during
the method call using transactional state.The `Actor` base class gives you access to some helper functions and saves you from writing some boiler-plate. You may
also implement `IActor` and use the `ActorTrait` as well.## Calling an Actor
In order to call an actor, simply call the `ActorProxy` and get a proxy object:
```php
get('/increment/{actorId}[/{amount:\d+}]', function(string $actorId, ActorProxy $actorProxy, int $amount = 1) {
$counter = $actorProxy->get(ICounter::class, $actorId);
$counter->increment($amount);
$counter->create_reminder('increment', new \Dapr\Actors\Reminder('increment', new DateInterval('PT10M'), data: 10 ));
});
$app->start();
```You can also call an actor without an interface:
```php
$client->invokeActorMethod(
httpMethod: 'GET',
actor: new \Dapr\Actors\ActorReference(id: 'id', actor_type: 'Counter'),
method: 'increment',
parameter: 1
);
```## Actor Limitations
1. There's no re-entrance to an actor, by default. You'll need to enable it in the `ActorConfig`
and [in Dapr](https://docs.dapr.io/operations/support/support-preview-features/).
2. By design, static functions don't work.
3. There's overhead cost in calling "getter" functions.More detail here: https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview/
# Pub/Sub
Delivering events around your application is an important aspect of any application. This is supported by Dapr, and
implemented in this SDK.## Publishing
In order to publish an event, you just instantiate the `Topic` object:
```php
get('/publish', function(\Dapr\Client\DaprClient $client) {
$topic = new \Dapr\PubSub\Topic(pubsub: 'pubsub', topic: 'topic', client: $client);
$topic->publish(['message' => 'arrive at dawn']);
});
$app->start();
```or you can use the new client like:
```php
$client->publishEvent(pubsubName: 'pubsub', topicName: 'topic', data: ['message' => 'arrive at dawn'], contentType: 'application/json');
```## Subscribing
```php
$app = \Dapr\App::create(configure: fn(\DI\ContainerBuilder $builder) => $builder->addDefinitions([
'dapr.subscriptions' => [new \Dapr\PubSub\Subscription('redis-pubsub', 'my-topic', '/receive-message')]
]));
$app->post('/receive-message', function(#[\Dapr\Attributes\FromBody] \Dapr\PubSub\CloudEvent $event) {
// do something
});
$app->start();
```# Serializing
If you need to register a custom serializer, you can completely override the built-in serializers on a per-type basis or
even the default serializers:```php
// register a custom type serializer
$app = \Dapr\App::create(configure: fn(\DI\ContainerBuilder $builder) => $builder->addDefinitions([
'dapr.serializers.custom' => [MyType::class => [MyType::class, 'serialize']],
'dapr.deserializers.custom' => [MyType::class => [MyType::class, 'deserialize']],
]));// get the serializer to do manual serializations
$app->get('/', function(\Dapr\Serialization\ISerializer $serializer) {
return $serializer->as_array('anything here');
});
$app->start();
```## Using the new client
```php
$client = \Dapr\Client\DaprClient::clientBuilder()
->withDeserializationConfig($configuration)
->withSerializationConfig($configuration)
->build()
```# Development
## Tests
Simply run `composer test` to run the unit tests. You can lint using `composer lint`.
## Integration tests
You need [`docker-compose`](https://docs.docker.com/compose/gettingstarted/) and [`jq`](https://stedolan.github.io/jq/)
Build and start the environment, then run the integration tests and clean up.
```bash
make
```You should see output like:
```json
{
"/test/actors": {
"status": {
"test completed successfully: ": "✔"
},
"results": {
"Empty actor should have no data: ": "✔",
"Actor should have data: ": "✔",
"Reminder should increment: ": "✔",
"time formats are delivered ok: ": "✔",
"Timer should increment: ": "✔",
"[object] saved array should match: ": "✔",
"[object] saved string should match: ": "✔",
"actor can return a simple value: ": "✔"
}
},
"/test/binding": {
"status": {
"test completed successfully: ": "✔"
},
"results": {
"we should have received at least one cron: ": "✔"
}
},
"/test/invoke": {
"status": {
"test completed successfully: ": "✔"
},
"results": {
"Should receive a 200 response: ": "✔",
"Static function should receive json string: ": "✔"
}
},
"/test/pubsub": {
"status": {
"test completed successfully: ": "✔"
},
"results": {
"simple-test": {
"sub received message: ": "✔",
"Received this data": {
"id": "57b4a889-3cbb-4d5d-a6fe-7574b097c34c",
"source": "dev",
"specversion": "1.0",
"type": "com.dapr.event.sent",
"datacontenttype": "application/json",
"data": [
"test_event"
],
"traceid": "00-222a0988ecf9d53a006a35f961229788-6706c11d588c6da2-00"
},
"should be valid cloud event: ": "✔"
},
"Testing custom cloud event": {
"sub received message: ": "✔",
"Received this raw data": {
"id": "123",
"source": "http://example.com",
"specversion": "1.0",
"type": "com.example.test",
"datacontenttype": "application/json",
"subject": "yolo",
"time": "2021-02-21T09:32:35+00:00Z",
"data": [
"yolo"
],
"traceid": "00-222a0988ecf9d53a006a35f961229788-a0880db7ab9a97d3-00"
},
"Expecting this data": {
"id": "123",
"source": "http://example.com",
"specversion": "1.0",
"type": "com.example.test",
"datacontenttype": "application/json",
"subject": "yolo",
"time": "2021-02-21T09:32:35+00:00Z",
"data": [
"yolo"
]
},
"Received this decoded data": {
"id": "123",
"source": "http://example.com",
"specversion": "1.0",
"type": "com.example.test",
"datacontenttype": "application/json",
"subject": "yolo",
"time": "2021-02-21T09:32:35+00:00Z",
"data": [
"yolo"
]
},
"Event should be the same event we sent, minus the trace id.: ": "✔"
},
"Publishing raw event": {
"sub received message: ": "✔",
"Received this data": {
"id": "01e9fef4-7774-4684-9249-cdce032a2713",
"source": "dev",
"specversion": "1.0",
"type": "com.dapr.event.sent",
"datacontenttype": "application/json",
"data": {
"datacontenttype": "text/xml",
"data": "User1user2hi",
"specversion": "1.0",
"type": "xml.message",
"source": "https://example.com/message",
"subject": "Test XML Message",
"id": "id-1234-5678-9101",
"time": "2020-09-23T06:23:21Z"
},
"traceid": "00-222a0988ecf9d53a006a35f961229788-d90a5a50ffa8c104-00"
}
},
"Binary response": {
"raw": {
"id": "3a5936e4-9bce-41e5-b344-f7e9bfb186f5",
"source": "dev",
"specversion": "1.0",
"type": "com.dapr.event.sent",
"datacontenttype": "application/json",
"data": "raw data",
"traceid": "00-222a0988ecf9d53a006a35f961229788-d579f3a753fa6b45-00"
},
"Data properly decoded: ": "✔"
}
}
},
"/test/state/concurrency": {
"status": {
"test completed successfully: ": "✔"
},
"results": {
"initial value correct: ": "✔",
"Starting from 0: ": "✔",
"last-write update succeeds: ": "✔",
"first-write update fails": "✔"
}
},
"/test/state": {
"status": {
"test completed successfully: ": "✔"
},
"results": {
"state is empty: ": "✔",
"initial state is correct: ": "✔",
"saved correct state: ": "✔",
"properly loaded saved state: ": "✔",
"prefix should work: ": "✔",
"single key read with default: ": "✔",
"single key write: ": "✔"
}
}
}
```