├── .gitattributes ├── .gitignore ├── .phive └── phars.xml ├── LICENSE ├── Makefile ├── composer.json ├── composer.lock ├── daprdocs ├── content │ └── en │ │ └── php-sdk-docs │ │ ├── _index.md │ │ ├── php-actors │ │ ├── _index.md │ │ └── php-actor-reference.md │ │ ├── php-app │ │ ├── _index.md │ │ └── php-unit-testing.md │ │ ├── php-pubsub │ │ └── _index.md │ │ ├── php-serialization.md │ │ └── php-state │ │ └── _index.md └── readme.md ├── docker-compose.yml ├── examples ├── actor │ ├── .env │ ├── .gitignore │ ├── Makefile │ ├── components │ │ └── statestore.yml │ ├── composer.json │ ├── composer.lock │ ├── docker-compose.yml │ ├── global-config.php │ ├── images │ │ ├── Caddyfile │ │ ├── fpm.conf │ │ └── opcache.ini │ ├── index.php │ ├── readme.md │ ├── services │ │ ├── actor │ │ │ ├── Counter.php │ │ │ ├── ICounter.php │ │ │ ├── State.php │ │ │ ├── config.php │ │ │ └── index.php │ │ └── client │ │ │ ├── ICounter.php │ │ │ ├── config.php │ │ │ └── index.php │ └── test.php ├── docker-compose.common.yml ├── images │ ├── Caddyfile │ ├── caddy.Dockerfile │ ├── fpm.conf │ ├── opcache.ini │ ├── service.Dockerfile │ └── xdebug.ini ├── laravel │ ├── .editorconfig │ ├── .env │ ├── .env.example │ ├── .gitattributes │ ├── .gitignore │ ├── .styleci.yml │ ├── README.md │ ├── app │ │ ├── Console │ │ │ └── Kernel.php │ │ ├── Exceptions │ │ │ └── Handler.php │ │ ├── Http │ │ │ ├── Controllers │ │ │ │ └── Controller.php │ │ │ ├── Kernel.php │ │ │ └── Middleware │ │ │ │ ├── Authenticate.php │ │ │ │ ├── EncryptCookies.php │ │ │ │ ├── PreventRequestsDuringMaintenance.php │ │ │ │ ├── RedirectIfAuthenticated.php │ │ │ │ ├── TrimStrings.php │ │ │ │ ├── TrustHosts.php │ │ │ │ ├── TrustProxies.php │ │ │ │ └── VerifyCsrfToken.php │ │ ├── Models │ │ │ ├── ExampleState.php │ │ │ └── User.php │ │ └── Providers │ │ │ ├── AppServiceProvider.php │ │ │ ├── AuthServiceProvider.php │ │ │ ├── BroadcastServiceProvider.php │ │ │ ├── DaprServiceProvider.php │ │ │ ├── EventServiceProvider.php │ │ │ └── RouteServiceProvider.php │ ├── artisan │ ├── bootstrap │ │ ├── app.php │ │ └── cache │ │ │ └── .gitignore │ ├── components │ │ ├── pubsub.yaml │ │ └── statestore.yaml │ ├── composer.json │ ├── composer.lock │ ├── config │ │ ├── app.php │ │ ├── auth.php │ │ ├── broadcasting.php │ │ ├── cache.php │ │ ├── cors.php │ │ ├── database.php │ │ ├── filesystems.php │ │ ├── hashing.php │ │ ├── logging.php │ │ ├── mail.php │ │ ├── queue.php │ │ ├── services.php │ │ ├── session.php │ │ └── view.php │ ├── database │ │ ├── .gitignore │ │ ├── factories │ │ │ └── UserFactory.php │ │ ├── migrations │ │ │ ├── 2014_10_12_000000_create_users_table.php │ │ │ ├── 2014_10_12_100000_create_password_resets_table.php │ │ │ └── 2019_08_19_000000_create_failed_jobs_table.php │ │ └── seeders │ │ │ └── DatabaseSeeder.php │ ├── docker-compose.yml │ ├── package.json │ ├── phpunit.xml │ ├── public │ │ ├── favicon.ico │ │ ├── index.php │ │ ├── robots.txt │ │ └── web.config │ ├── resources │ │ ├── css │ │ │ └── app.css │ │ ├── js │ │ │ ├── app.js │ │ │ └── bootstrap.js │ │ ├── lang │ │ │ └── en │ │ │ │ ├── auth.php │ │ │ │ ├── pagination.php │ │ │ │ ├── passwords.php │ │ │ │ └── validation.php │ │ └── views │ │ │ └── welcome.blade.php │ ├── routes │ │ ├── api.php │ │ ├── channels.php │ │ ├── console.php │ │ └── web.php │ ├── server.php │ ├── start.php │ ├── storage │ │ ├── app │ │ │ ├── .gitignore │ │ │ └── public │ │ │ │ └── .gitignore │ │ ├── framework │ │ │ ├── .gitignore │ │ │ ├── cache │ │ │ │ ├── .gitignore │ │ │ │ └── data │ │ │ │ │ └── .gitignore │ │ │ ├── sessions │ │ │ │ └── .gitignore │ │ │ ├── testing │ │ │ │ └── .gitignore │ │ │ └── views │ │ │ │ └── .gitignore │ │ └── logs │ │ │ └── .gitignore │ ├── test.php │ ├── tests │ │ ├── CreatesApplication.php │ │ ├── Feature │ │ │ └── ExampleTest.php │ │ ├── TestCase.php │ │ └── Unit │ │ │ └── ExampleTest.php │ └── webpack.mix.js └── secrets │ ├── .dockerignore │ ├── .env │ ├── .gitignore │ ├── Makefile │ ├── components │ ├── file-secrets.yml │ └── secrets.json │ ├── composer.json │ ├── composer.lock │ ├── deployments │ ├── client-service.yml │ ├── secret-service.yml │ ├── simple-secret.yml │ └── some-secrets.yml │ ├── docker-compose.yml │ ├── global-config.php │ ├── images │ ├── Caddyfile │ ├── fpm.conf │ └── opcache.ini │ ├── index.php │ ├── readme.md │ ├── services │ ├── client │ │ ├── config.php │ │ └── index.php │ └── secrets │ │ ├── config.php │ │ └── index.php │ └── test.php ├── phpunit.xml ├── psalm-baseline.xml ├── psalm.xml ├── psysh-bootstrap.php ├── psysh-config.php ├── readme.md ├── src ├── config.php └── lib │ ├── Actors │ ├── .phpstorm.meta.php │ ├── Actor.php │ ├── ActorConfig.php │ ├── ActorProxy.php │ ├── ActorReference.php │ ├── ActorRuntime.php │ ├── ActorState.php │ ├── ActorTrait.php │ ├── Attributes │ │ ├── DaprType.php │ │ ├── Delete.php │ │ ├── Get.php │ │ ├── Post.php │ │ └── Put.php │ ├── Generators │ │ ├── CachedGenerator.php │ │ ├── DynamicGenerator.php │ │ ├── ExistingOnly.php │ │ ├── FileGenerator.php │ │ ├── GenerateProxy.php │ │ ├── IGenerateProxy.php │ │ └── ProxyFactory.php │ ├── HealthCheck.php │ ├── IActor.php │ ├── IActorReference.php │ ├── Internal │ │ ├── Caches │ │ │ ├── CacheInterface.php │ │ │ ├── FileCache.php │ │ │ ├── KeyNotFound.php │ │ │ └── MemoryCache.php │ │ ├── InternalProxy.php │ │ └── KeyResponse.php │ ├── ReentrantConfig.php │ ├── Reminder.php │ └── Timer.php │ ├── App.php │ ├── Attributes │ └── FromBody.php │ ├── Client │ ├── AppId.php │ ├── BindingRequest.php │ ├── BindingResponse.php │ ├── DaprClient.php │ ├── DaprClientBuilder.php │ ├── DaprHttpClient.php │ ├── DeleteTransactionRequest.php │ ├── HttpActorTrait.php │ ├── HttpBindingTrait.php │ ├── HttpInvokeTrait.php │ ├── HttpPubSubTrait.php │ ├── HttpSecretsTrait.php │ ├── HttpStateTrait.php │ ├── HttpTokenTrait.php │ ├── MetadataResponse.php │ ├── PromiseHandlingTrait.php │ ├── StateTransactionRequest.php │ └── UpsertTransactionRequest.php │ ├── DaprClient.php │ ├── DaprResponse.php │ ├── Deserialization │ ├── .phpstorm.meta.php │ ├── Attributes │ │ ├── ArrayOf.php │ │ └── AsClass.php │ ├── DeserializationConfig.php │ ├── Deserializer.php │ ├── Deserializers │ │ ├── DateInterval.php │ │ ├── DateTime.php │ │ └── IDeserialize.php │ ├── IDeserializer.php │ └── InvokerParameterResolver.php │ ├── Formats.php │ ├── Middleware │ ├── Defaults │ │ ├── ActorToken.php │ │ ├── Response │ │ │ └── ApplicationJson.php │ │ ├── TokenAuth.php │ │ └── Tracing.php │ ├── IRequestMiddleware.php │ └── IResponseMiddleware.php │ ├── Mocks │ └── TestClient.php │ ├── PubSub │ ├── CloudEvent.php │ ├── Publish.php │ ├── Subscription.php │ ├── Subscriptions.php │ └── Topic.php │ ├── SecretManager.php │ ├── Serialization │ ├── Attributes │ │ └── AlwaysObject.php │ ├── ISerializer.php │ ├── SerializationConfig.php │ ├── Serializer.php │ └── Serializers │ │ ├── DateInterval.php │ │ ├── DateTime.php │ │ ├── ISerialize.php │ │ └── StateItem.php │ ├── State │ ├── Attributes │ │ └── StateStore.php │ ├── FileWriter.php │ ├── IManageState.php │ ├── Internal │ │ ├── StateHelpers.php │ │ └── Transaction.php │ ├── StateItem.php │ ├── StateManager.php │ ├── StateManagerOld.php │ ├── TransactionalState.php │ └── TransactionalStateOld.php │ ├── consistency │ ├── Consistency.php │ ├── EventualFirstWrite.php │ ├── EventualLastWrite.php │ ├── StrongFirstWrite.php │ └── StrongLastWrite.php │ └── exceptions │ ├── DaprException.php │ ├── Http │ ├── HttpException.php │ └── NotFound.php │ ├── SaveStateFailure.php │ └── StateAlreadyCommitted.php └── update-examples.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Don't include these files in the package 2 | /tests export-ignore 3 | /src/index.php export-ignore 4 | /.github export-ignore 5 | /components export-ignore 6 | /deploy export-ignore 7 | /images export-ignore 8 | .htaccess export-ignore 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.lsync.pid 3 | /make/ 4 | /.idea/ 5 | .phpunit.result.cache 6 | coverage.xml 7 | /tools/ 8 | -------------------------------------------------------------------------------- /.phive/phars.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | export GITHUB_SHA=latest 3 | export DAPR_VERSION=1.3.0-rc.1 4 | 5 | PHIVE=$(shell which phive || echo .phive/phive) 6 | 7 | .PHONY: integration-tests 8 | integration-tests: build tools 9 | docker-compose down -v 10 | docker-compose up & 11 | sleep 10 12 | curl --silent --output /tmp/test-results.json --write-out "%{http_code}" -H 'dapr-api-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' http://localhost:9502/do_tests 13 | docker-compose down -v 14 | cat /tmp/test-results.json | jq . 15 | 16 | .PHONY: stop 17 | docker-compose down -v 18 | 19 | composer.lock: composer.json 20 | composer update 21 | 22 | vendor/autoload.php: composer.lock 23 | composer install 24 | touch vendor/autoload.php 25 | 26 | .PHONY: build 27 | build: build-caddy build-tests 28 | 29 | .PHONY: build-tests 30 | build-tests: vendor/autoload.php 31 | docker build -t tests:latest -f images/tests.Dockerfile . 32 | 33 | .PHONY: build-caddy 34 | build-caddy: vendor/autoload.php 35 | docker build -t caddy:latest -f images/caddy.Dockerfile . 36 | 37 | $(PHIVE): 38 | wget -O phive.phar "https://phar.io/releases/phive.phar" 39 | wget -O phive.phar.asc "https://phar.io/releases/phive.phar.asc" 40 | gpg --keyserver hkps://keys.openpgp.org --recv-keys 0x9D8A98B29B2D5D79 41 | gpg --verify phive.phar.asc phive.phar 42 | rm phive.phar.asc 43 | chmod +x phive.phar 44 | mv phive.phar $(PHIVE) 45 | 46 | tools: .phive/phars.xml $(PHIVE) 47 | rm -rf tools && echo $(PHIVE) 48 | $(PHIVE) install --trust-gpg-keys 12CE0F1D262429A5,4AA394086372C20A 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dapr/php-sdk", 3 | "type": "library", 4 | "description": "Dapr Implementation in PHP", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Rob Landers", 9 | "email": "landers.robert@gmail.com" 10 | }, 11 | { 12 | "name": "Hendrik Heil", 13 | "email": "hendrik@ciidyr.com" 14 | } 15 | ], 16 | "require": { 17 | "ext-curl": "*", 18 | "ext-json": "*", 19 | "ext-mbstring": "*", 20 | "guzzlehttp/guzzle": "^7.3", 21 | "laminas/laminas-httphandlerrunner": "^1.3", 22 | "monolog/monolog": "^2.2", 23 | "nette/php-generator": "^3.5", 24 | "nikic/fast-route": "^1.3", 25 | "nyholm/psr7": "^1.3", 26 | "nyholm/psr7-server": "^1.0", 27 | "php": "^8.0", 28 | "php-di/invoker": "^2.3", 29 | "php-di/php-di": "^6.3", 30 | "psr/log": "^1.1|^2.0|^3.0", 31 | "psr/http-server-middleware": ">=1.0.1", 32 | "jetbrains/phpstorm-attributes": "1.0" 33 | }, 34 | "require-dev": { 35 | "ext-xdebug": "*", 36 | "phpunit/phpunit": "^9" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Dapr\\": "src/lib", 41 | "Dapr\\TestActors\\": "test/actors" 42 | } 43 | }, 44 | "scripts": { 45 | "test": "./vendor/bin/phpunit --testdox tests --coverage-clover coverage.xml tests --whitelist src", 46 | "start": [ 47 | "COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose build --pull", 48 | "docker-compose up -d" 49 | ], 50 | "dapr-init": [ 51 | "dapr init --runtime-version 1.1.0" 52 | ], 53 | "clean": [ 54 | "docker-compose stop", 55 | "docker-compose rm -fv" 56 | ], 57 | "integration-tests": [ 58 | "curl localhost:9502/do_tests" 59 | ], 60 | "lint": "./tools/psalm" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /daprdocs/content/en/php-sdk-docs/php-pubsub/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: docs 3 | title: "Publish and Subscribe with PHP" 4 | linkTitle: "Publish and Subscribe" 5 | weight: 1000 6 | description: How to use 7 | no_list: true 8 | --- 9 | 10 | With Dapr, you can publish anything, including cloud events. The SDK contains a simple cloud event implementation, but 11 | you can also just pass an array that conforms to the cloud event spec or use another library. 12 | 13 | ```php 14 | post('/publish', function(\Dapr\Client\DaprClient $daprClient) { 16 | $daprClient->publishEvent(pubsubName: 'pubsub', topicName: 'my-topic', data: ['something' => 'happened']); 17 | }); 18 | ``` 19 | 20 | For more information about publish/subscribe, check out [the howto]({{< ref howto-publish-subscribe.md >}}). 21 | 22 | ## Data content type 23 | 24 | The PHP SDK allows setting the data content type either when constructing a custom cloud event, or when publishing raw 25 | data. 26 | 27 | {{< tabs CloudEvent "Raw" >}} 28 | 29 | {{% codetab %}} 30 | 31 | ```php 32 | data = $xml; 35 | $event->data_content_type = 'application/xml'; 36 | ``` 37 | 38 | {{% /codetab %}} 39 | {{% codetab %}} 40 | 41 | ```php 42 | publishEvent(pubsubName: 'pubsub', topicName: 'my-topic', data: $raw_data, contentType: 'application/octet-stream'); 47 | ``` 48 | 49 | {{% alert title="Binary data" color="warning" %}} 50 | 51 | Only `application/octet-steam` is supported for binary data. 52 | 53 | {{% /alert %}} 54 | 55 | {{% /codetab %}} 56 | 57 | {{< /tabs >}} 58 | 59 | ## Receiving cloud events 60 | 61 | In your subscription handler, you can have the DI Container inject either a `Dapr\PubSub\CloudEvent` or an `array` into 62 | your controller. The former does some validation to ensure you have a proper event. If you need direct access to the 63 | data, or the events do not conform to the spec, use an `array`. 64 | -------------------------------------------------------------------------------- /daprdocs/content/en/php-sdk-docs/php-serialization.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: docs 3 | title: "Custom Serialization" 4 | linkTitle: "Custom Serializers" 5 | weight: 1000 6 | description: How to configure serialization 7 | no_list: true 8 | --- 9 | 10 | Dapr uses JSON serialization and thus (complex) type information is lost when sending/receiving data. 11 | 12 | ## Serialization 13 | 14 | When returning an object from a controller, passing an object to the `DaprClient`, or storing an object in a state store, 15 | only public properties are scanned and serialized. You can customize this behavior by implementing `\Dapr\Serialization\ISerialize`. 16 | For example, if you wanted to create an ID type that serialized to a string, you may implement it like so: 17 | 18 | ```php 19 | id; 29 | } 30 | } 31 | ``` 32 | 33 | This works for any type that we have full ownership over, however, it doesn't work for classes from libraries or PHP itself. 34 | For that, you need to register a custom serializer with the DI container: 35 | 36 | ```php 37 | [SomeClass::class => new SerializeSomeClass()], 50 | ]; 51 | ``` 52 | 53 | ## Deserialization 54 | 55 | Deserialization works exactly the same way, except the interface is `\Dapr\Deserialization\Deserializers\IDeserialize`. 56 | -------------------------------------------------------------------------------- /daprdocs/content/en/php-sdk-docs/php-state/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: docs 3 | title: "State Management with PHP" 4 | linkTitle: "State management" 5 | weight: 1000 6 | description: How to use 7 | no_list: true 8 | --- 9 | 10 | Dapr offers a great modular approach to using state in your application. The best way to learn the basics is to visit 11 | [the howto]({{< ref howto-get-save-state.md >}}). 12 | 13 | ## Metadata 14 | 15 | Many state components allow you to pass metadata to the component to control specific aspects of the component's 16 | behavior. The PHP SDK allows you to pass that metadata through: 17 | 18 | ```php 19 | run( 22 | fn(\Dapr\State\StateManager $stateManager) => 23 | $stateManager->save_state('statestore', new \Dapr\State\StateItem('key', 'value', metadata: ['port' => '112']))); 24 | 25 | // using the DaprClient 26 | $app->run(fn(\Dapr\Client\DaprClient $daprClient) => $daprClient->saveState(storeName: 'statestore', key: 'key', value: 'value', metadata: ['port' => '112'])) 27 | ``` 28 | 29 | This is an example of how you might pass the port metadata to [Cassandra]({{< ref setup-cassandra.md >}}). 30 | 31 | Every state operation allows passing metadata. 32 | 33 | ## Consistency/concurrency 34 | 35 | In the PHP SDK, there are four classes that represent the four different types of consistency and concurrency in Dapr: 36 | 37 | ```php 38 | }} 62 | 63 | {{% codetab %}} 64 | 65 | ```php 66 | run(function (TransactionObject $object ) { 72 | $object->begin(prefix: 'my-prefix-'); 73 | $object->key = 'value'; 74 | // commit to key `my-prefix-key` 75 | $object->commit(); 76 | }); 77 | ``` 78 | 79 | {{% /codetab %}} 80 | {{% codetab %}} 81 | 82 | ```php 83 | run(function(\Dapr\State\StateManager $stateManager) { 89 | $stateManager->load_object($obj = new StateObject(), prefix: 'my-prefix-'); 90 | // original value is from `my-prefix-key` 91 | $obj->key = 'value'; 92 | // save to `my-prefix-key` 93 | $stateManager->save_object($obj, prefix: 'my-prefix-'); 94 | }); 95 | ``` 96 | 97 | {{% /codetab %}} 98 | 99 | {{< /tabs >}} 100 | -------------------------------------------------------------------------------- /daprdocs/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapr/php-sdk/f0f37c39c7b95b6402c617bdfb6743b0a3b8c8cf/daprdocs/readme.md -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # A Docker Compose file for spinning up integration tests in CI, you can run these locally by running `make` 2 | version: "3" 3 | services: 4 | placement: 5 | image: daprio/dapr:${DAPR_VERSION} 6 | command: [ "./placement","-port","50006" ] 7 | dev: 8 | image: tests:${GITHUB_SHA} 9 | environment: 10 | DAPR_API_TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 11 | APP_API_TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 12 | depends_on: 13 | - placement 14 | ports: 15 | - 9502:80 16 | dev-caddy: 17 | image: caddy:${GITHUB_SHA} 18 | depends_on: 19 | - dev 20 | network_mode: service:dev 21 | dev-daprd: 22 | image: daprio/daprd:${DAPR_VERSION} 23 | environment: 24 | DAPR_API_TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 25 | APP_API_TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 26 | command: [ "./daprd","-app-id","dev","-app-port","80","-placement-host-address","placement:50006","-components-path","/components" ] 27 | volumes: 28 | - ./components:/components 29 | depends_on: 30 | - dev 31 | network_mode: service:dev 32 | redis: 33 | image: redis:latest 34 | -------------------------------------------------------------------------------- /examples/actor/.env: -------------------------------------------------------------------------------- 1 | DOCKER_USER=withinboredom 2 | DAPR_VERSION=1.7.0 3 | -------------------------------------------------------------------------------- /examples/actor/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ 3 | -------------------------------------------------------------------------------- /examples/actor/Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | include .env 4 | export $(shell sed 's/=.*//' .env) 5 | export GIT_SHA=$(shell git rev-parse HEAD) 6 | 7 | .PHONY: deps 8 | deps: service/actor-http service/actor-service service/client-http service/client-service 9 | @echo 'all done: run `make start` to get started' 10 | 11 | .PHONY: service/actor-service 12 | service/actor-service: images/opcache.ini images/fpm.conf 13 | docker build --pull --build-arg SERVICE=actor -f ../images/service.Dockerfile --target production -t php-actor-service:${GIT_SHA} . 14 | 15 | .PHONY: service/actor-http 16 | service/actor-http: images/Caddyfile 17 | docker build --pull --build-arg SERVICE=actor -f ../images/caddy.Dockerfile --target base -t php-actor-http:${GIT_SHA} . 18 | 19 | .PHONY: service/client-service 20 | service/client-service: images/opcache.ini images/opcache.ini 21 | docker build --pull --build-arg SERVICE=client -f ../images/service.Dockerfile --target production -t php-client-service:${GIT_SHA} . 22 | 23 | .PHONY: service/client-http 24 | service/client-http: images/Caddyfile 25 | docker build --pull --build-arg SERICE=client -f ../images/caddy.Dockerfile --target base -t php-client-http:${GIT_SHA} . 26 | 27 | .PHONY: start 28 | start: 29 | DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f docker-compose.yml -f ../docker-compose.common.yml up -d 30 | 31 | .PHONY: stop 32 | stop: 33 | docker-compose -f docker-compose.yml -f ../docker-compose.common.yml stop 34 | 35 | .PHONY: clean 36 | clean: stop 37 | docker-compose -f docker-compose.yml -f ../docker-compose.common.yml rm -f 38 | 39 | .PHONY: logs 40 | logs: 41 | docker-compose -f docker-compose.yml -f ../docker-compose.common.yml logs 42 | 43 | images/fpm.conf: ../images/fpm.conf 44 | mkdir -p images 45 | cp ../images/fpm.conf images/fpm.conf 46 | touch images/fpm.conf 47 | 48 | images/opcache.ini: ../images/opcache.ini 49 | mkdir -p images 50 | cp ../images/opcache.ini images/opcache.ini 51 | touch images/opcache.ini 52 | 53 | images/xdebug.ini: ../images/xdebug.ini 54 | mkdir -p images 55 | cp ../images/xdebug.ini images/xdebug.ini 56 | touch images/xdebug.ini 57 | 58 | images/Caddyfile: ../images/Caddyfile 59 | mkdir -p images 60 | cp ../images/Caddyfile images/Caddyfile 61 | touch images/Caddyfile 62 | -------------------------------------------------------------------------------- /examples/actor/components/statestore.yml: -------------------------------------------------------------------------------- 1 | apiVersion: dapr.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: statestore 5 | spec: 6 | type: state.redis 7 | metadata: 8 | - name: redisHost 9 | value: redis:6379 10 | - name: redisPassword 11 | value: "" 12 | - name: actorStateStore 13 | value: "true" 14 | -------------------------------------------------------------------------------- /examples/actor/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dapr/actor-example", 3 | "license": "MIT", 4 | "description": "A basic example demonstrating actors", 5 | "require": { 6 | "dapr/php-sdk": "dev-main" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/actor/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | actor-service: 4 | image: php-actor-service:${GIT_SHA} 5 | actor-http: 6 | image: php-actor-http:${GIT_SHA} 7 | depends_on: 8 | - actor-service 9 | network_mode: service:actor-service 10 | actor-daprd: 11 | image: daprio/daprd:${DAPR_VERSION} 12 | command: [ "./daprd","-app-id","actor","-app-port","80","-placement-host-address","placement:50006","-components-path","/components" ] 13 | volumes: 14 | - ./components:/components 15 | depends_on: 16 | - actor-http 17 | network_mode: service:actor-service 18 | client-service: 19 | image: php-client-service:${GIT_SHA} 20 | ports: 21 | - 8080:80 22 | client-http: 23 | image: php-client-http:${GIT_SHA} 24 | depends_on: 25 | - client-service 26 | network_mode: service:client-service 27 | client-daprd: 28 | image: daprio/daprd:${DAPR_VERSION} 29 | command: [ "./daprd","-app-id","client","-app-port","80","-placement-host-address","placement:50006","-components-path","/components" ] 30 | depends_on: 31 | - client-http 32 | network_mode: service:client-service 33 | volumes: 34 | - ./components:/components 35 | redis: 36 | image: 'redis:alpine' 37 | -------------------------------------------------------------------------------- /examples/actor/global-config.php: -------------------------------------------------------------------------------- 1 | $builder->addDefinitions( 10 | __DIR__.'/global-config.php', 11 | SERVICE_ROOT.'/config.php' 12 | )->enableCompilation(sys_get_temp_dir())->enableDefinitionCache() 13 | ); 14 | 15 | include SERVICE_ROOT.'/index.php'; 16 | 17 | $app->start(); 18 | -------------------------------------------------------------------------------- /examples/actor/readme.md: -------------------------------------------------------------------------------- 1 | # Secret Example 2 | 3 | In this example we illustrate a production service that returns secrets. It consists of two services: 4 | 5 | 1. [actor-service](services/actor/index.php): A simple counter actor that allows getting the current count or 6 | incrementing the count. 7 | 2. [client-service](services/client/index.php): A simple API to communicate with the actor. 8 | 9 | ## Running the example 10 | 11 | ### Docker Compose 12 | 13 | > Requirements: 14 | > - Docker Compose 15 | > - Docker 16 | > - `make` 17 | > - `jq` (optional) 18 | 19 | #### Build the images 20 | 21 | You'll need to build some custom images, do that using `make`. 22 | 23 | ```bash 24 | make 25 | ``` 26 | 27 | #### Start the services 28 | 29 | You'll also use `make` to start a docker-compose file which will spin up dapr, the actor service, and the client 30 | service. 31 | 32 | ```bash 33 | make start 34 | ``` 35 | 36 | #### Query an actor 37 | 38 | You can query the current state of an actor using `curl` (and `jq` to format the JSON response). You can also increment 39 | the actor. Here's an example using an actor with the id `hello_world`: 40 | 41 | ```bash 42 | curl -s http://localhost:8080/method/hello_world/get_count | jq . 43 | curl -s http://localhost:8080/method/hello_world/increment_and_get | jq . 44 | ``` 45 | 46 | #### Clean up 47 | 48 | Delete the containers: 49 | 50 | ```bash 51 | make clean 52 | ``` 53 | -------------------------------------------------------------------------------- /examples/actor/services/actor/Counter.php: -------------------------------------------------------------------------------- 1 | state->count; 22 | } 23 | 24 | #[Post] 25 | function increment_and_get(): int 26 | { 27 | return $this->state->count += 1; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/actor/services/actor/ICounter.php: -------------------------------------------------------------------------------- 1 | [Counter::class], 5 | ]; 6 | -------------------------------------------------------------------------------- /examples/actor/services/actor/index.php: -------------------------------------------------------------------------------- 1 | get( 8 | '/', 9 | fn() => [ 10 | 'links' => [ 11 | 'Method Invoke' => 'GET /method//', 12 | 'Methods' => [ 13 | 'get_count' => 'http://localhost:8080/method/hello_world/get_count', 14 | 'increment_and_get' => 'http://localhost:8080/method/hello_world/increment_and_get', 15 | ], 16 | ], 17 | ] 18 | ); 19 | 20 | $app->get( 21 | '/method/{actor_id}/{method}', 22 | fn(string $actor_id, string $method, ActorProxy $actorProxy) => $actorProxy 23 | ->get(ICounter::class, $actor_id)->$method() 24 | ); 25 | -------------------------------------------------------------------------------- /examples/actor/test.php: -------------------------------------------------------------------------------- 1 | 0, 7 | "http://localhost:8080/method/$id/increment_and_get" => 1, 8 | ]; 9 | 10 | $failed = false; 11 | 12 | for ($i = 0; $i < 5; $i++) { 13 | $logs = `GIT_SHA=t docker-compose logs actor-daprd`; 14 | if (str_contains($logs, 'placement tables updated, version: 1')) { 15 | echo "Running Tests!\n"; 16 | break; 17 | } 18 | echo "Waiting for actors to be registered...\n"; 19 | sleep(2); 20 | } 21 | 22 | foreach ($urls as $url => $expected) { 23 | echo "Calling $url: "; 24 | $result = json_decode(`curl -s $url`, true); 25 | if ($result === $expected) { 26 | echo "PASS\n"; 27 | } else { 28 | echo "FAILED\n"; 29 | echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 30 | $failed = true; 31 | } 32 | } 33 | 34 | exit($failed ? 1 : 0); 35 | -------------------------------------------------------------------------------- /examples/docker-compose.common.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | placement: 4 | image: daprio/dapr:${DAPR_VERSION} 5 | command: ["./placement","-port","50006"] 6 | -------------------------------------------------------------------------------- /examples/images/Caddyfile: -------------------------------------------------------------------------------- 1 | :80 2 | 3 | root * /app 4 | uri replace /assets/ /services/ui/src/assets/ 5 | file_server * { 6 | hide .git 7 | } 8 | php_fastcgi localhost:9000 9 | encode gzip 10 | -------------------------------------------------------------------------------- /examples/images/caddy.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM caddy AS base 2 | ARG SERVICE 3 | COPY services/$SERVICE /app/services/$SERVICE 4 | COPY index.php /app/index.php 5 | COPY global-config.php /app/global-config.php 6 | COPY images/Caddyfile /etc/caddy/Caddyfile 7 | -------------------------------------------------------------------------------- /examples/images/service.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.0-fpm AS base 2 | ENV VERSION=1 3 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/ 4 | RUN apt-get update && apt-get install -y wget git unzip && apt-get clean && rm -rf /var/cache/apt/lists 5 | RUN install-php-extensions curl intl zip sodium opcache apcu @composer && mkdir -p /app 6 | WORKDIR /app 7 | 8 | FROM base AS vendor 9 | COPY composer.json composer.json 10 | COPY composer.lock composer.lock 11 | RUN composer install --no-dev -o -n 12 | 13 | FROM base AS config 14 | ARG SERVICE 15 | ENV SERVICE=$SERVICE 16 | COPY services/$SERVICE services/$SERVICE 17 | COPY --from=vendor /app/vendor vendor 18 | COPY index.php index.php 19 | COPY global-config.php global-config.php 20 | 21 | FROM config AS production 22 | RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" 23 | ENV PHP_CLI_SERVER_WORKERS=100 24 | COPY images/opcache.ini /tmp/opcache.ini 25 | COPY images/fpm.conf /usr/local/etc/php-fpm.d/www.conf 26 | RUN cat /tmp/opcache.ini >> $PHP_INI_DIR/php.ini 27 | 28 | FROM production AS development 29 | ARG SERVICE 30 | COPY images/xdebug.ini /tmp/xdebug.ini 31 | ENV DBGP_IDEKEY=$SERVICE 32 | RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" && \ 33 | cd $(php -d 'display_errors=stderr' -r 'echo ini_get("extension_dir");') && \ 34 | mv /php-disabled/xdebug.so . && mv /php-disabled/docker-php-ext-xdebug.ini $PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini 35 | RUN cat /tmp/xdebug.ini >> $PHP_INI_DIR/php.ini 36 | COPY images/opcache.ini /tmp/opcache.ini 37 | RUN cat /tmp/opcache.ini >> $PHP_INI_DIR/php.ini 38 | -------------------------------------------------------------------------------- /examples/images/xdebug.ini: -------------------------------------------------------------------------------- 1 | [xdebug] 2 | ;xdebug.discover_client_host = true 3 | xdebug.mode = develop,debug 4 | xdebug.log = /tmp/xdebug.log 5 | xdebug.log_level = 0 6 | xdebug.client_host = host.docker.internal 7 | xdebug.start_with_request = true 8 | -------------------------------------------------------------------------------- /examples/laravel/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /examples/laravel/.env: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY=base64:jJfpP0ZYEiP46qjg6yZs51xj8iYqmRmf9plZ6ppmOAI= 4 | APP_DEBUG=true 5 | APP_URL=http://example-app.test 6 | 7 | LOG_CHANNEL=stack 8 | LOG_LEVEL=debug 9 | 10 | DB_CONNECTION=mysql 11 | DB_HOST=mysql 12 | DB_PORT=3306 13 | DB_DATABASE=example_app 14 | DB_USERNAME=sail 15 | DB_PASSWORD=password 16 | 17 | BROADCAST_DRIVER=log 18 | CACHE_DRIVER=file 19 | QUEUE_CONNECTION=sync 20 | SESSION_DRIVER=file 21 | SESSION_LIFETIME=120 22 | 23 | MEMCACHED_HOST=memcached 24 | 25 | REDIS_HOST=redis 26 | REDIS_PASSWORD=null 27 | REDIS_PORT=6379 28 | 29 | MAIL_MAILER=smtp 30 | MAIL_HOST=mailhog 31 | MAIL_PORT=1025 32 | MAIL_USERNAME=null 33 | MAIL_PASSWORD=null 34 | MAIL_ENCRYPTION=null 35 | MAIL_FROM_ADDRESS=null 36 | MAIL_FROM_NAME="${APP_NAME}" 37 | 38 | AWS_ACCESS_KEY_ID= 39 | AWS_SECRET_ACCESS_KEY= 40 | AWS_DEFAULT_REGION=us-east-1 41 | AWS_BUCKET= 42 | 43 | PUSHER_APP_ID= 44 | PUSHER_APP_KEY= 45 | PUSHER_APP_SECRET= 46 | PUSHER_APP_CLUSTER=mt1 47 | 48 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 49 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 50 | 51 | SCOUT_DRIVER=meilisearch 52 | MEILISEARCH_HOST=http://meilisearch:7700 53 | -------------------------------------------------------------------------------- /examples/laravel/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=stack 8 | LOG_LEVEL=debug 9 | 10 | DB_CONNECTION=mysql 11 | DB_HOST=127.0.0.1 12 | DB_PORT=3306 13 | DB_DATABASE=example_app 14 | DB_USERNAME=root 15 | DB_PASSWORD= 16 | 17 | BROADCAST_DRIVER=log 18 | CACHE_DRIVER=file 19 | QUEUE_CONNECTION=sync 20 | SESSION_DRIVER=file 21 | SESSION_LIFETIME=120 22 | 23 | MEMCACHED_HOST=127.0.0.1 24 | 25 | REDIS_HOST=127.0.0.1 26 | REDIS_PASSWORD=null 27 | REDIS_PORT=6379 28 | 29 | MAIL_MAILER=smtp 30 | MAIL_HOST=mailhog 31 | MAIL_PORT=1025 32 | MAIL_USERNAME=null 33 | MAIL_PASSWORD=null 34 | MAIL_ENCRYPTION=null 35 | MAIL_FROM_ADDRESS=null 36 | MAIL_FROM_NAME="${APP_NAME}" 37 | 38 | AWS_ACCESS_KEY_ID= 39 | AWS_SECRET_ACCESS_KEY= 40 | AWS_DEFAULT_REGION=us-east-1 41 | AWS_BUCKET= 42 | 43 | PUSHER_APP_ID= 44 | PUSHER_APP_KEY= 45 | PUSHER_APP_SECRET= 46 | PUSHER_APP_CLUSTER=mt1 47 | 48 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 49 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 50 | -------------------------------------------------------------------------------- /examples/laravel/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /examples/laravel/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | .env.backup 7 | .phpunit.result.cache 8 | docker-compose.override.yml 9 | Homestead.json 10 | Homestead.yaml 11 | npm-debug.log 12 | yarn-error.log 13 | .idea 14 | -------------------------------------------------------------------------------- /examples/laravel/.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | disabled: 4 | - no_unused_imports 5 | finder: 6 | not-name: 7 | - index.php 8 | - server.php 9 | js: 10 | finder: 11 | not-name: 12 | - webpack.mix.js 13 | css: true 14 | -------------------------------------------------------------------------------- /examples/laravel/README.md: -------------------------------------------------------------------------------- 1 | # Laravel Example and Dapr 2 | 3 | This shows how to use Laravel as a client and as a service. If you take a peek into 4 | the [docker-compose](./docker-compose.yml) file, you'll see the Laravel Sail app is repeated twice. This is because 5 | Laravel Sail is a single-threaded server, so it's not possible to invoke itself. So the `laravel.test` app runs as a 6 | front-end client and the `laravel.api` runs as an API server; but they're both the same code and configuration. 7 | 8 | ## Running the example 9 | 10 | 1. Install PHP 8.0+ 11 | 1. Run `composer install` 12 | 1. Run `DAPR_VERSION=1.2.0 ./vendor/bin/sail up` or whichever version of a dapr sidecar is most current 13 | 2. Go to [http://localhost](http://localhost) 14 | 3. Go to [http://localhost/welcome/name](http://localhost/welcome/name) changing the `name` part of the uri to a 15 | different string 16 | 4. Visit [http://localhost](http://localhost) to see the name 17 | 18 | # What's Included 19 | 20 | 1. A [state store component](./components/statestore.yaml) with prefixes turned off to share state between the two apps 21 | 2. A [simple dapr provider](./app/Providers/DaprServiceProvider.php) which configures the client and serializers 22 | 3. A [slightly modified view](./resources/views/welcome.blade.php) which displays the current state 23 | 4. An [api route](./routes/api.php) to show the API implementation that updates the name state 24 | 4. A [front-end route](./routes/web.php) to show how to update state and invoke other services 25 | 5. A [docker-compose.yml](./docker-compose.yml) file which shows how to configure `daprd` for testing 26 | 6. A [.env](./.env) file which works with Laravel Sail 27 | 28 | # Note 29 | 30 | It's not recommended to use Laravel as an Actor host. 31 | -------------------------------------------------------------------------------- /examples/laravel/app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 28 | } 29 | 30 | /** 31 | * Register the commands for the application. 32 | * 33 | * @return void 34 | */ 35 | protected function commands() 36 | { 37 | $this->load(__DIR__.'/Commands'); 38 | 39 | require base_path('routes/console.php'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/laravel/app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | reportable(function (Throwable $e) { 38 | // 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/laravel/app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | [ 33 | \App\Http\Middleware\EncryptCookies::class, 34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 35 | \Illuminate\Session\Middleware\StartSession::class, 36 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 37 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 38 | \App\Http\Middleware\VerifyCsrfToken::class, 39 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 40 | ], 41 | 42 | 'api' => [ 43 | 'throttle:api', 44 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 45 | ], 46 | ]; 47 | 48 | /** 49 | * The application's route middleware. 50 | * 51 | * These middleware may be assigned to groups or used individually. 52 | * 53 | * @var array 54 | */ 55 | protected $routeMiddleware = [ 56 | 'auth' => \App\Http\Middleware\Authenticate::class, 57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 58 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 59 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 60 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 61 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 62 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 63 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 64 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 65 | ]; 66 | } 67 | -------------------------------------------------------------------------------- /examples/laravel/app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/laravel/app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 26 | return redirect(RouteServiceProvider::HOME); 27 | } 28 | } 29 | 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/laravel/app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/laravel/app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | 'datetime', 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /examples/laravel/app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/laravel/app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton( 17 | DaprClient::class, 18 | fn(ContainerInterface $container) => DaprClient::clientBuilder()->useHttpClient( 19 | "http://localhost:".config('dapr.port', '3500') 20 | )->withLogger($container->get(LoggerInterface::class))->withSerializationConfig( 21 | $container->get(SerializationConfig::class) 22 | )->withDeserializationConfig($container->get(DeserializationConfig::class))->build() 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/laravel/app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | * 26 | * @return void 27 | */ 28 | public function boot() 29 | { 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/laravel/app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | configureRateLimiting(); 39 | 40 | $this->routes(function () { 41 | Route::prefix('api') 42 | ->middleware('api') 43 | ->namespace($this->namespace) 44 | ->group(base_path('routes/api.php')); 45 | 46 | Route::middleware('web') 47 | ->namespace($this->namespace) 48 | ->group(base_path('routes/web.php')); 49 | }); 50 | } 51 | 52 | /** 53 | * Configure the rate limiters for the application. 54 | * 55 | * @return void 56 | */ 57 | protected function configureRateLimiting() 58 | { 59 | RateLimiter::for('api', function (Request $request) { 60 | return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/laravel/artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /examples/laravel/bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /examples/laravel/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /examples/laravel/components/pubsub.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dapr.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: pubsub 5 | spec: 6 | type: pubsub.redis 7 | metadata: 8 | - name: redisHost 9 | value: redis:6379 10 | - name: redisPassword 11 | value: "" 12 | -------------------------------------------------------------------------------- /examples/laravel/components/statestore.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dapr.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: statestore 5 | spec: 6 | type: state.redis 7 | metadata: 8 | - name: redisHost 9 | value: redis:6379 10 | - name: redisPassword 11 | value: "" 12 | - name: actorStateStore 13 | value: "true" 14 | - name: keyPrefix 15 | value: "none" 16 | -------------------------------------------------------------------------------- /examples/laravel/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "type": "project", 4 | "description": "The Laravel Framework.", 5 | "keywords": ["framework", "laravel"], 6 | "license": "MIT", 7 | "require": { 8 | "php": "^8.0", 9 | "dapr/php-sdk": "^1.0", 10 | "fideloper/proxy": "^4.4", 11 | "fruitcake/laravel-cors": "^2.0", 12 | "guzzlehttp/guzzle": "^7.0.1", 13 | "laravel/framework": "^8.12", 14 | "laravel/tinker": "^2.5" 15 | }, 16 | "require-dev": { 17 | "facade/ignition": "^2.5", 18 | "fakerphp/faker": "^1.9.1", 19 | "laravel/sail": "^1.0.1", 20 | "mockery/mockery": "^1.4.2", 21 | "nunomaduro/collision": "^5.0", 22 | "phpunit/phpunit": "^9.3.3" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "App\\": "app/", 27 | "Database\\Factories\\": "database/factories/", 28 | "Database\\Seeders\\": "database/seeders/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Tests\\": "tests/" 34 | } 35 | }, 36 | "scripts": { 37 | "post-autoload-dump": [ 38 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 39 | "@php artisan package:discover --ansi" 40 | ], 41 | "post-root-package-install": [ 42 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 43 | ], 44 | "post-create-project-cmd": [ 45 | "@php artisan key:generate --ansi" 46 | ] 47 | }, 48 | "extra": { 49 | "laravel": { 50 | "dont-discover": [] 51 | } 52 | }, 53 | "config": { 54 | "optimize-autoloader": true, 55 | "preferred-install": "dist", 56 | "sort-packages": true 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true 60 | } 61 | -------------------------------------------------------------------------------- /examples/laravel/config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'useTLS' => true, 41 | ], 42 | ], 43 | 44 | 'ably' => [ 45 | 'driver' => 'ably', 46 | 'key' => env('ABLY_KEY'), 47 | ], 48 | 49 | 'redis' => [ 50 | 'driver' => 'redis', 51 | 'connection' => 'default', 52 | ], 53 | 54 | 'log' => [ 55 | 'driver' => 'log', 56 | ], 57 | 58 | 'null' => [ 59 | 'driver' => 'null', 60 | ], 61 | 62 | ], 63 | 64 | ]; 65 | -------------------------------------------------------------------------------- /examples/laravel/config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /examples/laravel/config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure as many filesystem "disks" as you wish, and you 24 | | may even configure multiple disks of the same driver. Defaults have 25 | | been setup for each driver as an example of the required options. 26 | | 27 | | Supported Drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | ], 37 | 38 | 'public' => [ 39 | 'driver' => 'local', 40 | 'root' => storage_path('app/public'), 41 | 'url' => env('APP_URL').'/storage', 42 | 'visibility' => 'public', 43 | ], 44 | 45 | 's3' => [ 46 | 'driver' => 's3', 47 | 'key' => env('AWS_ACCESS_KEY_ID'), 48 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 49 | 'region' => env('AWS_DEFAULT_REGION'), 50 | 'bucket' => env('AWS_BUCKET'), 51 | 'url' => env('AWS_URL'), 52 | 'endpoint' => env('AWS_ENDPOINT'), 53 | ], 54 | 55 | ], 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Symbolic Links 60 | |-------------------------------------------------------------------------- 61 | | 62 | | Here you may configure the symbolic links that will be created when the 63 | | `storage:link` Artisan command is executed. The array keys should be 64 | | the locations of the links and the values should be their targets. 65 | | 66 | */ 67 | 68 | 'links' => [ 69 | public_path('storage') => storage_path('app/public'), 70 | ], 71 | 72 | ]; 73 | -------------------------------------------------------------------------------- /examples/laravel/config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 1024, 48 | 'threads' => 2, 49 | 'time' => 2, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /examples/laravel/config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | 'after_commit' => false, 43 | ], 44 | 45 | 'beanstalkd' => [ 46 | 'driver' => 'beanstalkd', 47 | 'host' => 'localhost', 48 | 'queue' => 'default', 49 | 'retry_after' => 90, 50 | 'block_for' => 0, 51 | 'after_commit' => false, 52 | ], 53 | 54 | 'sqs' => [ 55 | 'driver' => 'sqs', 56 | 'key' => env('AWS_ACCESS_KEY_ID'), 57 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 58 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 59 | 'queue' => env('SQS_QUEUE', 'default'), 60 | 'suffix' => env('SQS_SUFFIX'), 61 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 62 | 'after_commit' => false, 63 | ], 64 | 65 | 'redis' => [ 66 | 'driver' => 'redis', 67 | 'connection' => 'default', 68 | 'queue' => env('REDIS_QUEUE', 'default'), 69 | 'retry_after' => 90, 70 | 'block_for' => null, 71 | 'after_commit' => false, 72 | ], 73 | 74 | ], 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Failed Queue Jobs 79 | |-------------------------------------------------------------------------- 80 | | 81 | | These options configure the behavior of failed queue job logging so you 82 | | can control which database and table are used to store the jobs that 83 | | have failed. You may change them to any database / table you wish. 84 | | 85 | */ 86 | 87 | 'failed' => [ 88 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 89 | 'database' => env('DB_CONNECTION', 'mysql'), 90 | 'table' => 'failed_jobs', 91 | ], 92 | 93 | ]; 94 | -------------------------------------------------------------------------------- /examples/laravel/config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /examples/laravel/config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /examples/laravel/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.sqlite-journal 3 | -------------------------------------------------------------------------------- /examples/laravel/database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 27 | 'email' => $this->faker->unique()->safeEmail, 28 | 'email_verified_at' => now(), 29 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 30 | 'remember_token' => Str::random(10), 31 | ]; 32 | } 33 | 34 | /** 35 | * Indicate that the model's email address should be unverified. 36 | * 37 | * @return \Illuminate\Database\Eloquent\Factories\Factory 38 | */ 39 | public function unverified() 40 | { 41 | return $this->state(function (array $attributes) { 42 | return [ 43 | 'email_verified_at' => null, 44 | ]; 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/laravel/database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->timestamp('email_verified_at')->nullable(); 21 | $table->string('password'); 22 | $table->rememberToken(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('users'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/laravel/database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/laravel/database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('uuid')->unique(); 19 | $table->text('connection'); 20 | $table->text('queue'); 21 | $table->longText('payload'); 22 | $table->longText('exception'); 23 | $table->timestamp('failed_at')->useCurrent(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('failed_jobs'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/laravel/database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/laravel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "mix", 6 | "watch": "mix watch", 7 | "watch-poll": "mix watch -- --watch-options-poll=1000", 8 | "hot": "mix watch --hot", 9 | "prod": "npm run production", 10 | "production": "mix --production" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.21", 14 | "laravel-mix": "^6.0.6", 15 | "lodash": "^4.17.19", 16 | "postcss": "^8.1.14" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/laravel/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/Unit 10 | 11 | 12 | ./tests/Feature 13 | 14 | 15 | 16 | 17 | ./app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/laravel/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapr/php-sdk/f0f37c39c7b95b6402c617bdfb6743b0a3b8c8cf/examples/laravel/public/favicon.ico -------------------------------------------------------------------------------- /examples/laravel/public/index.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class); 50 | 51 | $response = tap($kernel->handle( 52 | $request = Request::capture() 53 | ))->send(); 54 | 55 | $kernel->terminate($request, $response); 56 | -------------------------------------------------------------------------------- /examples/laravel/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /examples/laravel/public/web.config: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/laravel/resources/css/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dapr/php-sdk/f0f37c39c7b95b6402c617bdfb6743b0a3b8c8cf/examples/laravel/resources/css/app.css -------------------------------------------------------------------------------- /examples/laravel/resources/js/app.js: -------------------------------------------------------------------------------- 1 | require('./bootstrap'); 2 | -------------------------------------------------------------------------------- /examples/laravel/resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | window.axios = require('axios'); 10 | 11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 12 | 13 | /** 14 | * Echo exposes an expressive API for subscribing to channels and listening 15 | * for events that are broadcast by Laravel. Echo and event broadcasting 16 | * allows your team to easily build robust real-time web applications. 17 | */ 18 | 19 | // import Echo from 'laravel-echo'; 20 | 21 | // window.Pusher = require('pusher-js'); 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: process.env.MIX_PUSHER_APP_KEY, 26 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 27 | // forceTLS: true 28 | // }); 29 | -------------------------------------------------------------------------------- /examples/laravel/resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /examples/laravel/resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /examples/laravel/resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have emailed your password reset link!', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that email address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /examples/laravel/routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | 21 | Route::get('/invoke-example/{name}', function(string $name, Request $request, \Dapr\State\StateManager $stateManager, \Psr\Log\LoggerInterface $logger) { 22 | $state = new \App\Models\ExampleState(); 23 | $stateManager->load_object($state); 24 | $state->last_name_seen = $name; 25 | $logger->critical('Setting last seen name to {name}', ['name' => $name]); 26 | $stateManager->save_object($state); 27 | return ['setName' => $name]; 28 | }); 29 | -------------------------------------------------------------------------------- /examples/laravel/routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /examples/laravel/routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /examples/laravel/routes/web.php: -------------------------------------------------------------------------------- 1 | load_object($state); 21 | $state->page_views += 1; 22 | $stateManager->save_object($state); 23 | 24 | return view('welcome', ['page_views' => $state->page_views, 'last_name_seen' => $state->last_name_seen]); 25 | } 26 | ); 27 | 28 | Route::get( 29 | '/welcome/{name}', 30 | function (string $name, \Dapr\Client\DaprClient $client) { 31 | // todo: no security 32 | return $client->invokeMethod('GET', new \Dapr\Client\AppId('api'), "api/invoke-example/$name") 33 | ->getBody() 34 | ->getContents(); 35 | } 36 | ); 37 | -------------------------------------------------------------------------------- /examples/laravel/server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /examples/laravel/start.php: -------------------------------------------------------------------------------- 1 | 'http://localhost', 8 | 'page-views' => '1', 9 | 'name' => 'go to /welcome/{name} to see a name here', 10 | ], 11 | [ 12 | 'url' => 'http://localhost/welcome/test', 13 | 'page-views' => null, 14 | 'name' => null, 15 | ], 16 | [ 17 | 'url' => 'http://localhost', 18 | 'page-views' => '2', 19 | 'name' => 'test', 20 | ], 21 | ]; 22 | 23 | $failed = false; 24 | 25 | foreach ($urls as ['url' => $url, 'page-views' => $expected_page_views, 'name' => $expected_name]) { 26 | echo "Calling $url:\n"; 27 | $result = `curl -s $url`; 28 | 29 | if ( !empty($expected_page_views)) { 30 | echo "Checking page views are $expected_page_views: "; 31 | $view_pos = strpos($result, 'views: '); 32 | $views = explode(' ', substr($result, $view_pos, strlen('views: X')))[1] ?? null; 33 | if ($views === $expected_page_views) { 34 | echo "PASS\n"; 35 | } else { 36 | echo "FAILED\n"; 37 | echo $result; 38 | $failed = true; 39 | } 40 | } 41 | 42 | if ( !empty($expected_name)) { 43 | echo "Checking name matches '$expected_name': "; 44 | $name_pos = strpos($result, 'name: '); 45 | $name = explode(' ', substr($result, $name_pos, strlen('name: ') + strlen($expected_name)), 2)[1] ?? null; 46 | if ($name === $expected_name) { 47 | echo "PASS\n"; 48 | } else { 49 | echo "FAILED\n"; 50 | echo $result; 51 | $failed = true; 52 | } 53 | } 54 | } 55 | 56 | exit($failed ? 1 : 0); 57 | -------------------------------------------------------------------------------- /examples/laravel/tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/laravel/tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/laravel/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/laravel/webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel applications. By default, we are compiling the CSS 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/js/app.js', 'public/js') 15 | .postCss('resources/css/app.css', 'public/css', [ 16 | // 17 | ]); 18 | -------------------------------------------------------------------------------- /examples/secrets/.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /examples/secrets/.env: -------------------------------------------------------------------------------- 1 | DAPR_VERSION=1.2.0 2 | -------------------------------------------------------------------------------- /examples/secrets/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /examples/secrets/Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | export GIT_SHA=$(shell git rev-parse HEAD) 4 | include .env 5 | export $(shell sed 's/=.*//' .env) 6 | 7 | .PHONY: deps 8 | deps: php-secrets-service php-secrets-http docker-compose.yml php-secret-client-http php-secret-client-service 9 | 10 | .PHONY: php-secrets-service 11 | php-secrets-service: images/fpm.conf images/opcache.ini 12 | docker build -f ../images/service.Dockerfile -t php-secrets-service:$(GIT_SHA) --pull --build-arg SERVICE=secrets --target production . 13 | 14 | .PHONY: php-secrets-http 15 | php-secrets-http: images/Caddyfile 16 | docker build -f ../images/caddy.Dockerfile -t php-secrets-http:$(GIT_SHA) --pull --build-arg SERVICE=secrets --target base . 17 | 18 | .PHONY: php-secret-client-service 19 | php-secret-client-service: images/fpm.conf images/opcache.ini 20 | docker build -f ../images/service.Dockerfile -t php-secret-client-service:$(GIT_SHA) --pull --build-arg SERVICE=client --target production . 21 | 22 | .PHONY: php-secret-client-http 23 | php-secret-client-http: images/Caddyfile 24 | docker build -f ../images/caddy.Dockerfile -t php-secret-client-http:$(GIT_SHA) --pull --build-arg SERVICE=client --target base . 25 | 26 | .PHONY: start 27 | start: 28 | DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f docker-compose.yml -f ../docker-compose.common.yml up -d 29 | 30 | .PHONY: stop 31 | stop: 32 | docker-compose -f docker-compose.yml -f ../docker-compose.common.yml stop 33 | 34 | .PHONY: logs 35 | logs: 36 | docker-compose -f docker-compose.yml -f ../docker-compose.common.yml logs 37 | 38 | .PHONY: clean 39 | clean: stop 40 | docker-compose -f docker-compose.yml -f ../docker-compose.common.yml rm -f 41 | rm -rf images 42 | 43 | .PHONY: push 44 | push: deps 45 | docker-compose push 46 | 47 | .PHONY: deploy 48 | deploy: 49 | kubectl apply -f deployments/simple-secret.yml 50 | kubectl apply -f deployments/some-secrets.yml 51 | envsubst < deployments/secret-service.yml | kubectl apply -f - 52 | envsubst < deployments/client-service.yml | kubectl apply -f - 53 | 54 | .PHONY: deploy-delete 55 | deploy-delete: 56 | kubectl delete -f deployments/simple-secret.yml 57 | kubectl delete -f deployments/some-secrets.yml 58 | envsubst < deployments/secret-service.yml | kubectl delete -f - 59 | envsubst < deployments/client-service.yml | kubectl delete -f - 60 | 61 | images/fpm.conf: ../images/fpm.conf 62 | mkdir -p images 63 | cp ../images/fpm.conf images/fpm.conf 64 | touch images/fpm.conf 65 | 66 | images/opcache.ini: ../images/opcache.ini 67 | mkdir -p images 68 | cp ../images/opcache.ini images/opcache.ini 69 | touch images/opcache.ini 70 | 71 | images/xdebug.ini: ../images/xdebug.ini 72 | mkdir -p images 73 | cp ../images/xdebug.ini images/xdebug.ini 74 | touch images/xdebug.ini 75 | 76 | images/Caddyfile: ../images/Caddyfile 77 | mkdir -p images 78 | cp ../images/Caddyfile images/Caddyfile 79 | touch images/Caddyfile 80 | -------------------------------------------------------------------------------- /examples/secrets/components/file-secrets.yml: -------------------------------------------------------------------------------- 1 | apiVersion: dapr.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: file-secrets 5 | namespace: secret-example 6 | spec: 7 | type: secretstores.local.file 8 | version: v1 9 | metadata: 10 | - name: secretsFile 11 | value: /components/secrets.json 12 | - name: nestedSeparator 13 | value: "." 14 | -------------------------------------------------------------------------------- /examples/secrets/components/secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "some-secret": { 3 | "nested": "got the nested secret!", 4 | "other": "some other secret" 5 | }, 6 | "simple-secret": "got the simple secret!" 7 | } 8 | -------------------------------------------------------------------------------- /examples/secrets/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dapr/secret-example", 3 | "license": "MIT", 4 | "description": "A basic example demonstrating secret access", 5 | "require": { 6 | "dapr/php-sdk": "dev-main" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/secrets/deployments/client-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: client-service 6 | name: client 7 | spec: 8 | replicas: 5 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: client-service 12 | template: 13 | metadata: 14 | labels: 15 | app.kubernetes.io/name: client-service 16 | annotations: 17 | dapr.io/enabled: "true" 18 | dapr.io/app-id: client 19 | dapr.io/app-port: "80" 20 | dapr.io/max-concurrency: "100" 21 | spec: 22 | containers: 23 | - image: ${DOCKER_USER}/php-client-service:latest 24 | name: client 25 | imagePullPolicy: Always 26 | - image: ${DOCKER_USER}/php-client-http:latest 27 | name: client-http 28 | imagePullPolicy: Always 29 | -------------------------------------------------------------------------------- /examples/secrets/deployments/secret-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: secrets-service 6 | name: secrets 7 | spec: 8 | replicas: 5 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: secrets-service 12 | template: 13 | metadata: 14 | labels: 15 | app.kubernetes.io/name: secrets-service 16 | annotations: 17 | dapr.io/enabled: "true" 18 | dapr.io/app-id: secrets 19 | dapr.io/app-port: "80" 20 | dapr.io/max-concurrency: "100" 21 | spec: 22 | containers: 23 | - image: ${DOCKER_USER}/php-secrets-service:latest 24 | name: secrets 25 | imagePullPolicy: Always 26 | - image: ${DOCKER_USER}/php-secrets-http:latest 27 | name: secrets-http 28 | imagePullPolicy: Always 29 | -------------------------------------------------------------------------------- /examples/secrets/deployments/simple-secret.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: simple-secret 5 | type: Opaque 6 | data: 7 | data: Z290IGEgc2VjcmV0IGZyb20gazhz 8 | -------------------------------------------------------------------------------- /examples/secrets/deployments/some-secrets.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: some-secret 5 | type: Opaque 6 | data: 7 | nested: Z290IGEgc2VjcmV0IGZyb20gazhz 8 | -------------------------------------------------------------------------------- /examples/secrets/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | secrets-service: 4 | image: php-secrets-service:${GIT_SHA} 5 | build: 6 | args: 7 | SERVICE: secrets 8 | dockerfile: ../../examples/images/service.Dockerfile 9 | context: ../../examples/secrets 10 | target: production 11 | secrets-http: 12 | image: php-secrets-http:${GIT_SHA} 13 | build: 14 | args: 15 | SERVICE: secrets 16 | dockerfile: ../../examples/images/caddy.Dockerfile 17 | context: ../../examples/secrets 18 | depends_on: 19 | - secrets-service 20 | network_mode: service:secrets-service 21 | secrets-daprd: 22 | image: daprio/daprd:${DAPR_VERSION} 23 | command: [ "./daprd","-app-id","secrets","-app-port","80","-placement-host-address","placement:50006","-components-path","/components" ] 24 | volumes: 25 | - ./components:/components 26 | depends_on: 27 | - secrets-http 28 | network_mode: service:secrets-service 29 | client-service: 30 | image: php-secret-client-service:${GIT_SHA} 31 | build: 32 | args: 33 | SERVICE: client 34 | dockerfile: ../../examples/images/service.Dockerfile 35 | context: ../../examples/secrets 36 | target: production 37 | ports: 38 | - 8080:80 39 | client-http: 40 | image: php-secret-client-http:${GIT_SHA} 41 | build: 42 | args: 43 | SERVICE: client 44 | dockerfile: ../../examples/images/caddy.Dockerfile 45 | context: ../../examples/secrets 46 | depends_on: 47 | - client-service 48 | network_mode: service:client-service 49 | client-daprd: 50 | image: daprio/daprd:${DAPR_VERSION} 51 | command: [ "./daprd","-app-id","client","-app-port","80","-placement-host-address","placement:50006","-components-path","/components" ] 52 | depends_on: 53 | - client-http 54 | network_mode: service:client-service 55 | -------------------------------------------------------------------------------- /examples/secrets/global-config.php: -------------------------------------------------------------------------------- 1 | $builder->addDefinitions( 10 | __DIR__.'/global-config.php', 11 | SERVICE_ROOT.'/config.php' 12 | )->enableDefinitionCache()->enableCompilation(sys_get_temp_dir()) 13 | ); 14 | 15 | include SERVICE_ROOT.'/index.php'; 16 | 17 | $app->start(); 18 | -------------------------------------------------------------------------------- /examples/secrets/readme.md: -------------------------------------------------------------------------------- 1 | # Secret Example 2 | 3 | In this example we illustrate a production service that returns secrets. It consists of two services: 4 | 5 | 1. [secrets-service](services/secrets/index.php): this gets the secrets from the configured secrets store and returns it. 6 | 2. [client-service](services/client/index.php): Invokes the secret service and returns the secret. This is exposed locally. 7 | 8 | For development (with docker-compose), secrets are stored in [secrets.json](components/secrets.json) 9 | 10 | ## Running the example 11 | 12 | ### Docker Compose 13 | 14 | > Requirements: 15 | > - Docker Compose 16 | > - Docker 17 | > - `make` 18 | > - `jq` (optional) 19 | 20 | 1. Run `make` to build the images 21 | 2. Run `make start` to start the containers in the background 22 | 3. Run `curl -s localhost:8080/run | jq .` to call the client-service and return the secrets. 23 | 24 | ### Kubernetes 25 | 26 | > Requirements 27 | > - A configured kubernetes cluster 28 | > - Dapr installed on the cluster 29 | > - `make` 30 | > - A Docker Hub user 31 | 32 | 1. Update [the `.env` file](.env) with your Docker Hub username 33 | 2. Run `make push` to build and push the images 34 | 3. Run `make deploy` to deploy to Kubernetes 35 | 4. Wait for the pods to be running: `watch kubectl get pods` 36 | 4. In another terminal run `kubectl port-forward deployment/client 8080:80` 37 | 5. View the secrets using `curl -s localhost:8080/run | jq .` to call the client-service and return the secrets. 38 | -------------------------------------------------------------------------------- /examples/secrets/services/client/config.php: -------------------------------------------------------------------------------- 1 | get( 6 | '/run', 7 | function (DaprClient $client) { 8 | return $client->get('/invoke/secrets/method/list-secrets')->data; 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /examples/secrets/services/secrets/config.php: -------------------------------------------------------------------------------- 1 | get( 7 | '/list-secrets', 8 | function (LoggerInterface $logger, DaprClient $client) { 9 | $logger->critical('Fetching Secrets.'); 10 | try { 11 | $file_secrets = [ 12 | 'simple-secret' => $client->getSecret('file-secrets', 'simple-secret')['simple-secret'], 13 | 'nested-secret' => $client->getSecret('file-secrets', 'some-secret.nested')['some-secret.nested'], 14 | ]; 15 | } catch (\Dapr\exceptions\DaprException) { 16 | $k8s_secrets = [ 17 | 'simple-secret' => $client->getSecret('kubernetes', 'simple-secret')['data'], 18 | 'nested-secret' => $client->getSecret('kubernetes', 'some-secret')['nested'], 19 | ]; 20 | } 21 | $secrets = $file_secrets ?? $k8s_secrets ?? []; 22 | 23 | return [ 24 | 'simple_secret' => $secrets['simple-secret'] ?? null, 25 | 'nested_secret' => $secrets['nested-secret'] ?? null, 26 | ]; 27 | } 28 | ); 29 | -------------------------------------------------------------------------------- /examples/secrets/test.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'simple_secret' => 'got the simple secret!', 8 | 'nested_secret' => 'got the nested secret!', 9 | ], 10 | ]; 11 | 12 | $failed = false; 13 | 14 | foreach ($urls as $url => $expected) { 15 | echo "Calling $url: "; 16 | $result = json_decode(`curl -s $url`, true); 17 | if ($result === $expected) { 18 | echo "PASS\n"; 19 | } else { 20 | echo "FAILED\n"; 21 | echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 22 | $failed = true; 23 | } 24 | } 25 | 26 | exit($failed ? 1 : 0); 27 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | tests 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /psalm-baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /psysh-bootstrap.php: -------------------------------------------------------------------------------- 1 | build(); 4 | -------------------------------------------------------------------------------- /psysh-config.php: -------------------------------------------------------------------------------- 1 | [__DIR__ . '/vendor/autoload.php', __DIR__ . '/psysh-bootstrap.php'], 5 | 'startupMessage' => sprintf('$client configured') 6 | ]; 7 | -------------------------------------------------------------------------------- /src/lib/Actors/.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | id; 24 | } 25 | 26 | public function remind(string $name, Reminder $data): void 27 | { 28 | } 29 | 30 | public function on_activation(): void 31 | { 32 | } 33 | 34 | public function on_deactivation(): void 35 | { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/Actors/ActorProxy.php: -------------------------------------------------------------------------------- 1 | logger?->debug('Getting actor proxy for {i}||{id}', ['i' => $interface, 'id' => $id]); 44 | 45 | $reflected_interface = new ReflectionClass($interface); 46 | $type = $override_type ?? ($reflected_interface->getAttributes( 47 | DaprType::class 48 | )[0] ?? null)?->newInstance()->type; 49 | 50 | if (empty($type)) { 51 | $this->logger?->critical('{i} is missing a DaprType attribute', ['i' => $interface]); 52 | throw new LogicException("$interface must have a DaprType attribute"); 53 | } 54 | 55 | $generator = $this->proxyFactory->get_generator($interface, $type); 56 | 57 | return $generator->get_proxy($id); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/lib/Actors/ActorReference.php: -------------------------------------------------------------------------------- 1 | get_id(); 30 | 31 | if ($id === null) { 32 | throw new LogicException('actor(proxy) must implement get_id()'); 33 | } 34 | 35 | $detected_dapr_type = self::get_dapr_type($actor); 36 | 37 | $dapr_type = $actor->DAPR_TYPE ?? $detected_dapr_type?->type; 38 | 39 | if ($dapr_type === null) { 40 | throw new LogicException('Missing DaprType attribute on '.$actor::class); 41 | } 42 | 43 | return new self(id: $id, actor_type: $dapr_type); 44 | } 45 | 46 | private static function get_dapr_type(object|string $type): DaprType|null 47 | { 48 | $reflector = new ReflectionClass($type); 49 | /** 50 | * @var DaprType|null $type_attribute 51 | */ 52 | $type_attribute = ($reflector->getAttributes(DaprType::class)[0] ?? null)?->newInstance(); 53 | 54 | return $type_attribute; 55 | } 56 | 57 | public static function deserialize(mixed $value, IDeserializer $deserializer): mixed 58 | { 59 | return new ActorReference(id: $value['ActorId'], actor_type: $value['ActorType']); 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | public function bind(string $interface, ProxyFactory $proxy_factory): mixed 66 | { 67 | return $proxy_factory->get_generator($interface, $this->actor_type)->get_proxy( 68 | $this->id 69 | ); 70 | } 71 | 72 | /** 73 | * @inheritDoc 74 | */ 75 | public function get_actor_id(): string 76 | { 77 | return $this->id; 78 | } 79 | 80 | /** 81 | * @inheritDoc 82 | */ 83 | public function get_actor_type(): string 84 | { 85 | return $this->actor_type; 86 | } 87 | 88 | /** 89 | * @param ActorReference $value 90 | * @param ISerializer $serializer 91 | * 92 | * @return array 93 | */ 94 | public function serialize(mixed $value, ISerializer $serializer): array 95 | { 96 | return ['ActorId' => $value->id, 'ActorType' => $value->actor_type]; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/lib/Actors/Attributes/DaprType.php: -------------------------------------------------------------------------------- 1 | cache_dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.'dapr-proxy-cache'.DIRECTORY_SEPARATOR; 29 | } 30 | 31 | /** 32 | * Set the cache directory 33 | * 34 | * @param string $dir 35 | */ 36 | public function set_cache_dir(string $dir) { 37 | $this->cache_dir = $dir; 38 | } 39 | 40 | public function get_proxy(string $id): object 41 | { 42 | if ( ! class_exists($this->get_full_class_name())) { 43 | $file_generator = new FileGenerator($this->interface, $this->dapr_type, $this->client); 44 | $file = $file_generator->generate_file(); 45 | if ( ! is_dir($this->cache_dir)) { 46 | mkdir($this->cache_dir); 47 | } 48 | $filename = $this->cache_dir.$this->get_short_class_name(); 49 | FileWriter::write($filename, $file); 50 | require_once $filename; 51 | } 52 | 53 | return parent::get_proxy($id); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/lib/Actors/Generators/ExistingOnly.php: -------------------------------------------------------------------------------- 1 | get_full_class_name()); 40 | $proxy = $reflection->newInstance($this->client); 41 | $proxy->id = $id; 42 | 43 | return $proxy; 44 | } 45 | 46 | /** 47 | * @codeCoverageIgnore Never happens 48 | * @param Method $method 49 | */ 50 | protected function generate_failure_method(Method $method): void 51 | { 52 | throw new LogicException(); 53 | } 54 | 55 | /** 56 | * @codeCoverageIgnore Never happens 57 | * @param Method $method 58 | * @param string $id 59 | */ 60 | protected function generate_proxy_method(Method $method, string $id): void 61 | { 62 | throw new LogicException(); 63 | } 64 | 65 | /** 66 | * @codeCoverageIgnore Never happens 67 | * @param Method $method 68 | * @param string $id 69 | */ 70 | protected function generate_get_id(Method $method, string $id): void 71 | { 72 | throw new LogicException(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/lib/Actors/Generators/GenerateProxy.php: -------------------------------------------------------------------------------- 1 | getName()) { 32 | 'remind', 'on_activation', 'on_deactivation' => $this->generate_failure_method($method), 33 | 'create_reminder', 'get_reminder', 'delete_reminder', 'create_timer', 'delete_timer' => null, 34 | 'get_id' => $this->generate_get_id($method, $id), 35 | default => $this->generate_proxy_method($method, $id), 36 | }; 37 | } 38 | 39 | /** 40 | * Generate a method that shouldn't be called from outside the actor. 41 | * 42 | * @param Method $method The method 43 | * 44 | * @return Method 45 | */ 46 | protected abstract function generate_failure_method(Method $method); 47 | 48 | /** 49 | * Write a method to get the current actor id. 50 | * 51 | * @param Method $method 52 | * @param string $id 53 | * 54 | * @return Method 55 | */ 56 | protected abstract function generate_get_id(Method $method, string $id); 57 | 58 | /** 59 | * Write a method that calls the actor. 60 | * 61 | * @param Method $method 62 | * @param string $id 63 | * 64 | * @return Method 65 | */ 66 | protected abstract function generate_proxy_method(Method $method, string $id); 67 | 68 | /** 69 | * @param ClassType $interface 70 | * 71 | * @return Method[] available methods for the interface 72 | */ 73 | protected function get_methods(ClassType $interface): array 74 | { 75 | return array_merge($interface->getMethods(), ClassType::from(IActor::class)->getMethods()); 76 | } 77 | 78 | protected function get_full_class_name(): string 79 | { 80 | return "\\".$this->get_namespace()."\\".$this->get_short_class_name(); 81 | } 82 | 83 | protected function get_namespace(): string 84 | { 85 | return "Dapr\\Proxies"; 86 | } 87 | 88 | protected function get_short_class_name(): string 89 | { 90 | $internal_type = preg_replace('/[^a-zA-Z0-9_]*/', '', $this->dapr_type); 91 | 92 | return 'dapr_proxy_'.$internal_type; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/lib/Actors/Generators/IGenerateProxy.php: -------------------------------------------------------------------------------- 1 | $interface, 'dapr_type' => $dapr_type]; 44 | 45 | return match ($this->mode) { 46 | ProxyFactory::DYNAMIC => new DynamicGenerator($interface, $dapr_type, $this->client), 47 | ProxyFactory::GENERATED_CACHED => new CachedGenerator($interface, $dapr_type, $this->client), 48 | ProxyFactory::GENERATED => new FileGenerator($interface, $dapr_type, $this->client), 49 | ProxyFactory::ONLY_EXISTING => new ExistingOnly($interface, $dapr_type, $this->client), 50 | default => throw new InvalidArgumentException('mode must be a supported mode'), 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lib/Actors/HealthCheck.php: -------------------------------------------------------------------------------- 1 | reference, $this->state_name); 23 | $base_dir = self::get_base_path($this->reference->get_actor_type(), $this->reference->get_actor_id()); 24 | if ( ! file_exists($base_dir)) { 25 | mkdir($base_dir, recursive: true); 26 | } 27 | $this->state_name = mb_ereg_replace('([^\w\s\d\-_~,;\[\]\(\).])', '', $this->state_name) . ''; 28 | $this->state_name = mb_ereg_replace('([\.]{2,})', '', $this->state_name) . ''; 29 | $this->cache_file = $base_dir.DIRECTORY_SEPARATOR.$this->state_name.'.actor'; 30 | $this->unserialize_cache(); 31 | } 32 | 33 | private static function get_base_path(string $dapr_type, string $actor_id): string 34 | { 35 | return sys_get_temp_dir().DIRECTORY_SEPARATOR. 36 | 'actor-cache'.DIRECTORY_SEPARATOR.$dapr_type.DIRECTORY_SEPARATOR.$actor_id; 37 | } 38 | 39 | /** 40 | * @inheritDoc 41 | */ 42 | private function unserialize_cache() 43 | { 44 | if (file_exists($this->cache_file)) { 45 | $this->data = unserialize(file_get_contents($this->cache_file)); 46 | } 47 | } 48 | 49 | public static function clear_actor(string $dapr_type, string $actor_id): void 50 | { 51 | $path = self::get_base_path($dapr_type, $actor_id); 52 | if ( ! file_exists($path)) { 53 | return; 54 | } 55 | foreach (scandir($path) as $file) { 56 | if ($file === '.' || $file === '..') { 57 | continue; 58 | } 59 | unlink($path.DIRECTORY_SEPARATOR.$file); 60 | } 61 | rmdir($path); 62 | } 63 | 64 | /** 65 | * @inheritDoc 66 | */ 67 | public function flush_cache(): void 68 | { 69 | $this->serialize_cache(); 70 | } 71 | 72 | /** 73 | * @inheritDoc 74 | */ 75 | private function serialize_cache() 76 | { 77 | if ($this->data === []) { 78 | if (file_exists($this->cache_file)) { 79 | unlink($this->cache_file); 80 | } 81 | 82 | return; 83 | } 84 | 85 | FileWriter::write($this->cache_file, serialize($this->data)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/lib/Actors/Internal/Caches/KeyNotFound.php: -------------------------------------------------------------------------------- 1 | has_key($key)) { 28 | return $this->data[$key]; 29 | } 30 | throw new KeyNotFound(); 31 | } 32 | 33 | /** 34 | * @inheritDoc 35 | */ 36 | public function set_key(string $key, mixed $data): void 37 | { 38 | $this->data[$key] = $data; 39 | } 40 | 41 | /** 42 | * @inheritDoc 43 | */ 44 | public function evict(string $key): void 45 | { 46 | unset($this->data[$key]); 47 | } 48 | 49 | /** 50 | * @inheritDoc 51 | */ 52 | public function reset(): void 53 | { 54 | $this->data = []; 55 | } 56 | 57 | /** 58 | * @inheritDoc 59 | */ 60 | public function flush_cache(): void 61 | { 62 | } 63 | 64 | public function has_key(string $key): bool 65 | { 66 | return array_key_exists($key, $this->data); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/lib/Actors/Internal/InternalProxy.php: -------------------------------------------------------------------------------- 1 | $name, $arguments); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/Actors/Internal/KeyResponse.php: -------------------------------------------------------------------------------- 1 | "string", 'period' => "string", 'data' => "false|string"])] 72 | public function to_array(): array 73 | { 74 | return [ 75 | 'dueTime' => Formats::normalize_interval($this->due_time), 76 | 'period' => Formats::normalize_interval($this->period), 77 | 'data' => json_encode($this->data), 78 | ]; 79 | } 80 | 81 | public function serialize(mixed $value, ISerializer $serializer): mixed 82 | { 83 | if ($this->repetitions >= 0) { 84 | return [ 85 | 'dueTime' => Formats::normalize_interval($this->due_time), 86 | 'period' => "R{$this->repetitions}/" . $serializer->as_array($this->period), 87 | 'data' => $serializer->as_json($this->data), 88 | ]; 89 | } 90 | 91 | return [ 92 | 'dueTime' => Formats::normalize_interval($this->due_time), 93 | 'period' => Formats::normalize_interval($this->period), 94 | 'data' => $serializer->as_json($this->data) 95 | ]; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/lib/Actors/Timer.php: -------------------------------------------------------------------------------- 1 | to_array(); 32 | } 33 | 34 | #[ArrayShape(['dueTime' => "string", 'period' => "string", 'callback' => "string", 'data' => "array|null"])] 35 | public function to_array(): array 36 | { 37 | return [ 38 | 'dueTime' => Formats::normalize_interval($this->due_time), 39 | 'period' => Formats::normalize_interval($this->period), 40 | 'callback' => $this->callback, 41 | 'data' => $this->data, 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/Attributes/FromBody.php: -------------------------------------------------------------------------------- 1 | namespace) ? $this->id : "{$this->id}.{$this->namespace}"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/Client/BindingRequest.php: -------------------------------------------------------------------------------- 1 | $metadata 17 | */ 18 | public function __construct(public BindingRequest $request, public mixed $data, public array $metadata) 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/Client/DaprClientBuilder.php: -------------------------------------------------------------------------------- 1 | deserializationConfig, $this->serializationConfig, $this->logger); 31 | } 32 | 33 | public function withSerializationConfig(SerializationConfig $serializationConfig): self 34 | { 35 | return new self($this->defaultHttpHost, $this->deserializationConfig, $serializationConfig, $this->logger); 36 | } 37 | 38 | public function withDeserializationConfig(DeserializationConfig $deserializationConfig): self 39 | { 40 | return new self($this->defaultHttpHost, $deserializationConfig, $this->serializationConfig, $this->logger); 41 | } 42 | 43 | public function withLogger(LoggerInterface $logger): self 44 | { 45 | return new self($this->defaultHttpHost, $this->deserializationConfig, $this->serializationConfig, $logger); 46 | } 47 | 48 | public function build(): DaprClient 49 | { 50 | return new DaprHttpClient( 51 | $this->defaultHttpHost, 52 | new Deserializer($this->deserializationConfig, $this->logger), 53 | new Serializer($this->serializationConfig, $this->logger), 54 | $this->logger 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/Client/DaprHttpClient.php: -------------------------------------------------------------------------------- 1 | baseHttpUri, '/')) { 35 | $this->baseHttpUri = rtrim($this->baseHttpUri, '/'); 36 | } 37 | $options = [ 38 | 'base_uri' => $this->baseHttpUri, 39 | 'allow_redirects' => false, 40 | 'headers' => [ 41 | 'User-Agent' => 'DaprPHPSDK/v1.2', 42 | 'Accept' => 'application/json', 43 | 'Content-Type' => 'application/json' 44 | ] 45 | ]; 46 | 47 | if ($this->getDaprToken() !== null) { 48 | $options['headers']['dapr-api-token'] = $this->getDaprToken(); 49 | } 50 | 51 | if (!empty(ActorToken::$token)) { 52 | $options['headers']['Dapr-Reentrancy-Id'] = &ActorToken::$token; 53 | } 54 | 55 | $this->httpClient = new Client($options); 56 | } 57 | 58 | public function isDaprHealthy(): bool 59 | { 60 | try { 61 | $result = $this->httpClient->get('/v1.0/healthz'); 62 | if (204 === $result->getStatusCode()) { 63 | return true; 64 | } 65 | return false; 66 | } catch (\Throwable $exception) { 67 | return false; 68 | } 69 | } 70 | 71 | public function getMetadata(): MetadataResponse|null 72 | { 73 | try { 74 | $result = $this->httpClient->get('/v1.0/metadata'); 75 | return $this->deserializer->from_json(MetadataResponse::class, $result->getBody()->getContents()); 76 | } catch (\Throwable $exception) { 77 | return null; 78 | } 79 | } 80 | 81 | public function shutdown(bool $afterRequest = true): void 82 | { 83 | $shutdown = fn() => $this->httpClient->post('/v1.0/shutdown'); 84 | if ($afterRequest) { 85 | register_shutdown_function($shutdown); 86 | return; 87 | } 88 | 89 | $shutdown(); 90 | } 91 | 92 | protected function getHttpClient(): Client 93 | { 94 | return $this->httpClient; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/lib/Client/DeleteTransactionRequest.php: -------------------------------------------------------------------------------- 1 | invokeBindingAsync($bindingRequest, $dataType)->wait(); 28 | } 29 | 30 | public function invokeBindingAsync(BindingRequest $bindingRequest, string $dataType = 'array'): PromiseInterface 31 | { 32 | return $this->handlePromise( 33 | $this->httpClient->putAsync( 34 | '/v1.0/bindings/' . rawurlencode($bindingRequest->bindingName), 35 | [ 36 | 'body' => $this->serializer->as_json( 37 | [ 38 | 'data' => $bindingRequest->data, 39 | 'metadata' => $bindingRequest->metadata, 40 | 'operation' => $bindingRequest->operation 41 | ] 42 | ) 43 | ] 44 | ), 45 | fn(ResponseInterface $response) => new BindingResponse( 46 | $bindingRequest, 47 | $this->deserializer->from_json($dataType, $response->getBody()->getContents()), 48 | $response->getHeaders() 49 | ) 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/Client/HttpInvokeTrait.php: -------------------------------------------------------------------------------- 1 | handlePromise( 35 | $this->invokeMethodAsync($httpMethod, $appId, $methodName, $data, $metadata) 36 | )->wait(); 37 | } 38 | 39 | public function invokeMethodAsync( 40 | string $httpMethod, 41 | AppId $appId, 42 | string $methodName, 43 | mixed $data = null, 44 | array $metadata = [] 45 | ): PromiseInterface { 46 | $options = []; 47 | if (!empty($data)) { 48 | $options['body'] = $this->serializer->as_json($data); 49 | } 50 | $options['headers'] = $metadata; 51 | $appId = rawurlencode($appId->getAddress()); 52 | $methodName = rawurlencode($methodName); 53 | $methodName = str_replace('%2F', '/', $methodName); 54 | return $this->handlePromise( 55 | $this->httpClient->requestAsync($httpMethod, "/v1.0/invoke/$appId/method/$methodName", $options) 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/lib/Client/HttpPubSubTrait.php: -------------------------------------------------------------------------------- 1 | publishEventAsync($pubsubName, $topicName, $data, $metadata, $contentType)->wait(true); 34 | } 35 | 36 | public function publishEventAsync( 37 | string $pubsubName, 38 | string $topicName, 39 | mixed $data, 40 | array $metadata = [], 41 | string $contentType = 'application/json' 42 | ): PromiseInterface { 43 | $options = [ 44 | 'query' => array_merge( 45 | ...array_map(fn($key, $value) => ["metadata.$key" => $value], array_keys($metadata), $metadata) 46 | ), 47 | 'body' => $this->serializer->as_json($data), 48 | 'headers' => [ 49 | 'Content-Type' => $contentType, 50 | ] 51 | ]; 52 | $pubsubName = rawurlencode($pubsubName); 53 | $topicName = rawurlencode($topicName); 54 | return $this->handlePromise($this->httpClient->postAsync("/v1.0/publish/$pubsubName/$topicName", $options)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/Client/HttpSecretsTrait.php: -------------------------------------------------------------------------------- 1 | getSecretAsync($storeName, $key, $metadata)->wait(); 26 | } 27 | 28 | public function getSecretAsync(string $storeName, string $key, array $metadata = []): PromiseInterface 29 | { 30 | $storeName = rawurlencode($storeName); 31 | $key = rawurlencode($key); 32 | return $this->handlePromise( 33 | $this->httpClient->getAsync( 34 | "/v1.0/secrets/$storeName/$key", 35 | [ 36 | 'query' => array_merge( 37 | ...array_map( 38 | fn($key, $value) => ["metadata.$key" => $value], 39 | array_keys($metadata), 40 | $metadata 41 | ) 42 | ) 43 | ] 44 | ), 45 | fn(ResponseInterface $response) => $this->deserializer->from_json( 46 | 'array', 47 | $response->getBody()->getContents() 48 | ) 49 | ); 50 | } 51 | 52 | public function getBulkSecret(string $storeName, array $metadata = []): array 53 | { 54 | return $this->getBulkSecretAsync($storeName, $metadata)->wait(); 55 | } 56 | 57 | public function getBulkSecretAsync(string $storeName, array $metadata = []): PromiseInterface 58 | { 59 | $storeName = rawurlencode($storeName); 60 | return $this->handlePromise( 61 | $this->httpClient->getAsync( 62 | "/v1.0/secrets/$storeName/bulk", 63 | [ 64 | 'query' => array_merge( 65 | ...array_map( 66 | fn($key, $value) => ["metadata.$key" => $value], 67 | array_keys($metadata), 68 | $metadata 69 | ) 70 | ) 71 | ] 72 | ), 73 | fn(ResponseInterface $response) => $this->deserializer->from_json( 74 | 'array', 75 | $response->getBody()->getContents() 76 | ) 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/lib/Client/HttpTokenTrait.php: -------------------------------------------------------------------------------- 1 | $response instanceof DaprException ? throw $response : $response; 26 | } 27 | if (empty($errorTransformer)) { 28 | $errorTransformer = fn(\Throwable $exception) => match ($exception::class) { 29 | ServerException::class, ClientException::class => throw new DaprException( 30 | $exception->hasResponse() 31 | ? $exception->getResponse()->getBody()->getContents() 32 | : $exception->getMessage(), 33 | $exception->getCode(), 34 | $exception 35 | ), 36 | default => throw $exception 37 | }; 38 | } 39 | return $closure->then( 40 | $transformResult, 41 | $errorTransformer 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/Client/StateTransactionRequest.php: -------------------------------------------------------------------------------- 1 | deserializers[\DateInterval::class])) { 26 | $this->add(\DateInterval::class, new DateInterval()); 27 | } 28 | if (empty($this->deserializers[\DateTime::class])) { 29 | $this->add(\DateTime::class, new DateTime()); 30 | } 31 | } 32 | 33 | /** 34 | * Adds a deserializer for a given type 35 | * 36 | * @param string $type 37 | * @param IDeserialize $deserializer 38 | */ 39 | public function add(string $type, IDeserialize $deserializer) 40 | { 41 | $this->deserializers[$type] = $deserializer; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/Deserialization/Deserializers/DateInterval.php: -------------------------------------------------------------------------------- 1 | getParameters(); 30 | 31 | if ( ! empty($resolvedParameters)) { 32 | $parameters = array_diff_key($parameters, $resolvedParameters); 33 | } 34 | 35 | foreach ($parameters as $index => $parameter) { 36 | if ( ! empty($parameter->getAttributes(FromBody::class))) { 37 | $body = json_decode($this->request->getBody()->getContents(), true); 38 | 39 | $resolvedParameters[$index] = $this->deserializer->detect_from_parameter($parameter, $body); 40 | } 41 | } 42 | 43 | return $resolvedParameters; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/Formats.php: -------------------------------------------------------------------------------- 1 | m || $diff->y) { 38 | throw new LogicException('Interval cannot use months or years with Dapr'); 39 | } 40 | 41 | return $diff->format(self::FROM_INTERVAL); 42 | } 43 | 44 | public static function coalesce(DateInterval $interval): bool|DateInterval 45 | { 46 | $from = new DateTime(); 47 | $to = clone $from; 48 | $to = $to->add($interval); 49 | $diff = $to->diff($from, true); 50 | $diff->h += $diff->d * self::DAY_IN_HOURS; 51 | $diff->d = 0; 52 | 53 | return $diff; 54 | } 55 | 56 | /** 57 | * @param string $dapr_interval 58 | * 59 | * @return DateInterval|null 60 | * @throws Exception 61 | */ 62 | public static function from_dapr_interval(string $dapr_interval): ?DateInterval 63 | { 64 | if (empty($dapr_interval)) { 65 | return null; 66 | } 67 | 68 | $parts = array_combine( 69 | preg_split('/[0-9]/', $dapr_interval, 0, PREG_SPLIT_NO_EMPTY), 70 | preg_split('/[a-z]/', $dapr_interval, 0, PREG_SPLIT_NO_EMPTY) 71 | ); 72 | $interval = ['PT']; 73 | $seconds = 0.0; 74 | foreach ($parts as $time => $value) { 75 | $seconds += match ($time) { 76 | 'ns' => ((float)$value) * self::NANOSECOND_TO_SECOND, 77 | 'us', 'µs' => ((float)$value) * self::MICROSECOND_TO_SECOND, 78 | 'ms' => ((float)$value) * self::MILLISECOND_TO_SECOND, 79 | 's' => (float)$value, 80 | default => 0, 81 | }; 82 | $interval[] = match ($time) { 83 | 'm' => $value . 'M', 84 | 'h' => $value . 'H', 85 | default => null 86 | }; 87 | } 88 | if ($seconds > 0) { 89 | $interval[] = $seconds . 'S'; 90 | } 91 | 92 | return new DateInterval(implode('', $interval)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/lib/Middleware/Defaults/ActorToken.php: -------------------------------------------------------------------------------- 1 | hasHeader('Dapr-Reentrancy-Id')) { 22 | self::$token = $request->getHeader('Dapr-Reentrancy-Id'); 23 | } else { 24 | self::$token = []; 25 | } 26 | 27 | return $request; 28 | } 29 | 30 | public function response(ResponseInterface $response): ResponseInterface 31 | { 32 | if (!empty(self::$token)) { 33 | return $response->withHeader('Dapr-Reentrancy-Id', self::$token); 34 | } 35 | 36 | return $response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/Middleware/Defaults/Response/ApplicationJson.php: -------------------------------------------------------------------------------- 1 | hasHeader('Content-Type')) { 17 | return $response; 18 | } 19 | 20 | return $response->withHeader('Content-Type', 'application/json'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/Middleware/Defaults/TokenAuth.php: -------------------------------------------------------------------------------- 1 | getAppToken(); 21 | if ($token === null) { 22 | return $request; 23 | } 24 | 25 | if (!$request->hasHeader('dapr-api-token')) { 26 | throw new NotFound(); 27 | } 28 | 29 | if (!hash_equals($token, $request->getHeader('dapr-api-token')[0] ?? '')) { 30 | throw new NotFound(); 31 | } 32 | 33 | return $request; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/Middleware/Defaults/Tracing.php: -------------------------------------------------------------------------------- 1 | hasHeader('traceparent')) { 22 | $this->trace_parent = $request->getHeader('traceparent')[0]; 23 | $this->trace_state = $request->hasHeader('tracestate') ? $request->getHeader('tracestate')[0] : null; 24 | } 25 | 26 | return $request; 27 | } 28 | 29 | public function response(ResponseInterface $response): ResponseInterface 30 | { 31 | if ($this->trace_parent !== null) { 32 | $response = $response->withHeader('traceparent', $this->trace_parent); 33 | if ($this->trace_state !== null) { 34 | $response = $response->withHeader('tracestate', $this->trace_state); 35 | } 36 | } 37 | 38 | return $response; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/Middleware/IRequestMiddleware.php: -------------------------------------------------------------------------------- 1 | factory->make( 43 | Topic::class, 44 | [ 45 | 'pubsub' => $this->pubsub, 46 | 'topic' => $topic, 47 | 'client' => $this->container->get(\Dapr\DaprClient::class) 48 | ] 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/PubSub/Subscription.php: -------------------------------------------------------------------------------- 1 | as_array($this->subscriptions); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/PubSub/Topic.php: -------------------------------------------------------------------------------- 1 | logger !== null) { 37 | $this->logger->debug('Sending {event} to {topic}', ['event' => $event, 'topic' => $this->topic]); 38 | } elseif ($this->client instanceof NewClient) { 39 | $this->client->logger->debug('Sending {event} to {topic}', ['event' => $event, 'topic' => $this->topic]); 40 | } 41 | if ($event instanceof CloudEvent) { 42 | $content_type = 'application/cloudevents+json'; 43 | $this->client->extra_headers = [ 44 | 'Content-Type: application/cloudevents+json', 45 | ]; 46 | 47 | $event = $event->to_array(); 48 | } 49 | 50 | if ($this->client instanceof DaprClient) { 51 | try { 52 | $this->client->post("/publish/{$this->pubsub}/{$this->topic}", $event, $metadata); 53 | 54 | $this->client->extra_headers = ['Content-Type: ' . $content_type]; 55 | 56 | return true; 57 | } catch (DaprException) { // @codeCoverageIgnoreStart 58 | return false; 59 | } // @codeCoverageIgnoreEnd 60 | } 61 | 62 | if ($this->client instanceof NewClient) { 63 | try { 64 | $this->client->publishEvent($this->pubsub, $this->topic, $event, $metadata ?? [], $content_type); 65 | return true; 66 | } catch (DaprException) { 67 | return false; 68 | } 69 | } 70 | 71 | return false; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/lib/SecretManager.php: -------------------------------------------------------------------------------- 1 | logger->debug('Retrieving all secrets from {secret_store}', ['secret_store' => $secret_store]); 32 | $result = $this->client->get("/secrets/$secret_store/bulk"); 33 | self::handle_response_code($result->code); 34 | 35 | return $result->data; 36 | } 37 | 38 | /** 39 | * Throw exceptions on errors 40 | * 41 | * @param int $code 42 | * 43 | * @throws DaprException 44 | */ 45 | private function handle_response_code(int $code): void 46 | { 47 | switch ($code) { 48 | case 200: 49 | case 204: 50 | return; 51 | case 400: 52 | throw new DaprException('Secret store missing or not configured'); 53 | case 403: 54 | throw new DaprException('Access denied'); 55 | case 500: 56 | throw new DaprException('Failed to get secret or no secret store defined'); 57 | } 58 | } 59 | 60 | /** 61 | * Retrieve a secret from the store. 62 | * 63 | * @param string $secret_store The store to get the secret from. 64 | * @param string $name The name of the secret to get. 65 | * @param array $parameters Optional parameters for the secret store 66 | * 67 | * @return array 68 | * @throws DaprException 69 | */ 70 | public function retrieve(string $secret_store, string $name, array $parameters = []): ?array 71 | { 72 | $this->logger->debug( 73 | 'Retrieving secret {name} from {secret_store}', 74 | ['name' => $name, 'secret_store' => $secret_store] 75 | ); 76 | $result = $this->client->get("/secrets/$secret_store/$name", $parameters); 77 | $this->handle_response_code($result->code); 78 | 79 | return $result->data; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/lib/Serialization/Attributes/AlwaysObject.php: -------------------------------------------------------------------------------- 1 | serializers[\DateInterval::class])) { 24 | $this->add(\DateInterval::class, new DateInterval()); 25 | } 26 | if (empty($this->serializers[\DateTime::class])) { 27 | $this->add(\DateTime::class, new DateTime()); 28 | } 29 | if (empty($this->serializers[StateItem::class])) { 30 | $this->add(StateItem::class, new Serializers\StateItem()); 31 | } 32 | } 33 | 34 | public function add(string $type, ISerialize $serializer): void 35 | { 36 | $this->serializers[$type] = $serializer; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/Serialization/Serializers/DateInterval.php: -------------------------------------------------------------------------------- 1 | format('P%yY%mM%dDT%hH%iM%sS%fF')), 21 | 'PT' 22 | ) ?: self::DEFAULT_DT; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/Serialization/Serializers/DateTime.php: -------------------------------------------------------------------------------- 1 | format(DATE_W3C); 18 | } 19 | throw new InvalidArgumentException('Date time is not a date time'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/Serialization/Serializers/ISerialize.php: -------------------------------------------------------------------------------- 1 | $value->key, 18 | 'value' => $serializer->as_array($value->value), 19 | ]; 20 | if (isset($value->etag)) { 21 | $item['etag'] = $value->etag; 22 | $item['options'] = [ 23 | 'consistency' => $value->consistency->get_consistency(), 24 | 'concurrency' => $value->consistency->get_concurrency(), 25 | ]; 26 | } 27 | if ( ! empty($value->metadata)) { 28 | $item['metadata'] = $value->metadata; 29 | } 30 | 31 | return $item; 32 | } 33 | 34 | return $value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/State/Attributes/StateStore.php: -------------------------------------------------------------------------------- 1 | getAttributes(StateStore::class) as $attribute) { 25 | return $attribute->newInstance(); 26 | } 27 | throw new LogicException('Tried to load state without a Dapr\State\Attributes\StateStore attribute'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/State/Internal/Transaction.php: -------------------------------------------------------------------------------- 1 | transaction); 39 | usort($transaction, fn($a, $b) => $a['order'] <=> $b['order']); 40 | 41 | return array_map( 42 | function ($a) { 43 | unset($a['order']); 44 | if (isset($a['request']['value'])) { 45 | $a['request']['value'] = $this->serializer->as_array($a['request']['value']); 46 | } 47 | 48 | return $a; 49 | }, 50 | $transaction 51 | ); 52 | } 53 | 54 | public function is_empty(): bool 55 | { 56 | return empty($this->transaction); 57 | } 58 | 59 | /** 60 | * Upsert a value in a transaction 61 | * 62 | * @param string $key 63 | * @param mixed $value 64 | */ 65 | public function upsert(string $key, mixed $value): void 66 | { 67 | $this->transaction[$key] = [ 68 | 'order' => $this->counter++, 69 | 'operation' => 'upsert', 70 | 'request' => [ 71 | 'key' => $key, 72 | 'value' => $value, 73 | ], 74 | ]; 75 | } 76 | 77 | /** 78 | * Delete a value in a transaction 79 | * 80 | * @param string $key 81 | */ 82 | public function delete(string $key): void 83 | { 84 | $this->transaction[$key] = [ 85 | 'order' => $this->counter++, 86 | 'operation' => 'delete', 87 | 'request' => [ 88 | 'key' => $key, 89 | ], 90 | ]; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/lib/State/StateItem.php: -------------------------------------------------------------------------------- 1 | consistency)) { 23 | $this->consistency = new StrongLastWrite(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/consistency/Consistency.php: -------------------------------------------------------------------------------- 1 | dapr_error_code = $array['errorCode']; 32 | break; 33 | } 34 | 35 | $original_exception->file = $array['file'] ?? $backtrace[1]['file']; 36 | $original_exception->line = $array['line'] ?? $backtrace[1]['line']; 37 | 38 | return $original_exception; 39 | } 40 | 41 | /** 42 | * @param Exception|null $exception 43 | * 44 | * @return array|null 45 | */ 46 | public static function serialize_to_array(?Exception $exception): ?array 47 | { 48 | if ($exception === null || ! ($exception instanceof Exception)) { 49 | return null; 50 | } 51 | 52 | return [ 53 | 'message' => $exception->getMessage(), 54 | 'errorCode' => get_class($exception), 55 | 'file' => $exception->getFile(), 56 | 'line' => $exception->getLine(), 57 | 'inner' => self::serialize_to_array($exception->getPrevious()), 58 | ]; 59 | } 60 | 61 | public function get_dapr_error_code(): string 62 | { 63 | return $this->dapr_error_code; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/exceptions/Http/HttpException.php: -------------------------------------------------------------------------------- 1 | require?->{'dapr/php-sdk'}; 15 | if ($php_sdk === null) { 16 | continue; 17 | } 18 | if (!empty($sha)) { 19 | $sha = "#$sha"; 20 | } 21 | $composer->require->{'dapr/php-sdk'} = "dev-$branch$sha"; 22 | file_put_contents( 23 | "examples/$example/composer.json", 24 | json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS) . "\n" 25 | ); 26 | chdir("examples/$example"); 27 | echo `composer update`; 28 | chdir(__DIR__); 29 | } 30 | --------------------------------------------------------------------------------