This is an automated email from the ASF dual-hosted git repository.

hubcio pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 2c286679a feat(php): add SDK examples component (#3368)
2c286679a is described below

commit 2c286679a28994e09509aaeba5f1a563b6326e44
Author: Diaconu Radu-Mihai <[email protected]>
AuthorDate: Fri May 29 16:13:01 2026 +0300

    feat(php): add SDK examples component (#3368)
---
 .github/config/components.yml             | 12 ++++
 .github/workflows/_test_examples.yml      | 41 ++++++++++++++
 examples/php/README.md                    | 39 +++++++++++++
 examples/php/basic/consumer.php           | 31 +++++++++++
 examples/php/basic/producer.php           | 33 +++++++++++
 examples/php/getting-started/consumer.php | 31 +++++++++++
 examples/php/getting-started/producer.php | 33 +++++++++++
 examples/php/src/common.php               | 91 +++++++++++++++++++++++++++++++
 scripts/run-examples-from-readme.sh       | 48 +++++++++++++++-
 9 files changed, 356 insertions(+), 3 deletions(-)

diff --git a/.github/config/components.yml b/.github/config/components.yml
index 0e20b897a..fea06a63a 100644
--- a/.github/config/components.yml
+++ b/.github/config/components.yml
@@ -402,6 +402,18 @@ components:
       - "scripts/utils.sh"
     tasks: ["examples-python"]
 
+  examples-php:
+    depends_on:
+      - "rust-server"
+      - "rust-sdk" # All SDKs depend on core SDK changes
+      - "sdk-php"
+      - "ci-infrastructure"
+    paths:
+      - "examples/php/**"
+      - "scripts/run-examples-from-readme.sh"
+      - "scripts/utils.sh"
+    tasks: ["examples-php"]
+
   examples-node:
     depends_on:
       - "rust-server"
diff --git a/.github/workflows/_test_examples.yml 
b/.github/workflows/_test_examples.yml
index 01454e1f5..28942a81e 100644
--- a/.github/workflows/_test_examples.yml
+++ b/.github/workflows/_test_examples.yml
@@ -65,6 +65,29 @@ jobs:
         if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-python'
         uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # 
v8.1.0
 
+      - name: Install PHP build dependencies
+        if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-php'
+        shell: bash
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y --no-install-recommends \
+            clang \
+            libclang-dev \
+            libhwloc-dev \
+            libssl-dev \
+            php8.3-cli \
+            php8.3-dev \
+            pkg-config
+
+          php_bin="$(command -v php8.3)"
+          php_config="$(command -v php-config8.3)"
+          {
+            echo "PHP=${php_bin}"
+            echo "PHP_CONFIG=${php_config}"
+          } >> "$GITHUB_ENV"
+          "${php_bin}" --version
+          "${php_config}" --version
+
       - name: Cache uv
         if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-python'
         uses: actions/cache@v5
@@ -123,6 +146,20 @@ jobs:
             fi
           fi
 
+      - name: Build PHP extension for examples
+        if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-php'
+        shell: bash
+        run: |
+          export CARGO_TARGET_DIR="${GITHUB_WORKSPACE}/target"
+          cargo build --manifest-path foreign/php/Cargo.toml
+          extension="$(find target/debug -maxdepth 1 -name 'libiggy_php.so' 
-print -quit)"
+          if [ -z "$extension" ]; then
+            echo "PHP extension was not produced"
+            exit 1
+          fi
+          echo "PHP_IGGY_EXTENSION=$(realpath "$extension")" >> "$GITHUB_ENV"
+          ls -lh "$extension"
+
       - name: Build Node SDK for examples
         if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-node'
         run: |
@@ -150,6 +187,10 @@ jobs:
         if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-python'
         run: ./scripts/run-examples-from-readme.sh --language python
 
+      - name: Run PHP examples
+        if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-php'
+        run: ./scripts/run-examples-from-readme.sh --language php
+
       - name: Run Node.js examples
         if: startsWith(inputs.component, 'examples-') && inputs.task == 
'examples-node'
         run: ./scripts/run-examples-from-readme.sh --language node
diff --git a/examples/php/README.md b/examples/php/README.md
new file mode 100644
index 000000000..58e0abde6
--- /dev/null
+++ b/examples/php/README.md
@@ -0,0 +1,39 @@
+# Iggy PHP Examples
+
+This directory contains sample applications that show how to use the Apache
+Iggy PHP SDK extension.
+
+## Running Examples
+
+Start the server from the repository root:
+
+```bash
+cargo run --bin iggy-server -- --fresh --with-default-root-credentials
+```
+
+From `examples/php`, build the PHP extension and point PHP at it:
+
+```bash
+(cd ../../foreign/php && cargo build)
+export 
PHP_IGGY_EXTENSION="$(pwd)/../../foreign/php/target/debug/libiggy_php.so"
+```
+
+On macOS, use `../../foreign/php/target/debug/libiggy_php.dylib` instead.
+
+### Getting Started
+
+```bash
+php -d 
extension="${PHP_IGGY_EXTENSION:-../../foreign/php/target/debug/libiggy_php.so}"
 getting-started/producer.php
+php -d 
extension="${PHP_IGGY_EXTENSION:-../../foreign/php/target/debug/libiggy_php.so}"
 getting-started/consumer.php
+```
+
+### Basic Usage
+
+```bash
+php -d 
extension="${PHP_IGGY_EXTENSION:-../../foreign/php/target/debug/libiggy_php.so}"
 basic/producer.php
+php -d 
extension="${PHP_IGGY_EXTENSION:-../../foreign/php/target/debug/libiggy_php.so}"
 basic/consumer.php
+```
+
+The examples use `IGGY_CONNECTION_STRING` when it is set. Otherwise they build
+`iggy+tcp://iggy:[email protected]:8090` from `IGGY_HOST`, `IGGY_PORT`,
+`IGGY_USERNAME`, and `IGGY_PASSWORD`, which can be set individually.
diff --git a/examples/php/basic/consumer.php b/examples/php/basic/consumer.php
new file mode 100644
index 000000000..81c61870e
--- /dev/null
+++ b/examples/php/basic/consumer.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+declare(strict_types=1);
+
+require_once __DIR__ . '/../src/common.php';
+
+$stream = 'php-basic-stream';
+$topic = 'php-basic-topic';
+$client = iggy_client();
+
+ensure_stream_and_topic($client, $stream, $topic);
+print_polled_messages($client, $stream, $topic, 3);
diff --git a/examples/php/basic/producer.php b/examples/php/basic/producer.php
new file mode 100644
index 000000000..eac864333
--- /dev/null
+++ b/examples/php/basic/producer.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+declare(strict_types=1);
+
+require_once __DIR__ . '/../src/common.php';
+
+$stream = 'php-basic-stream';
+$topic = 'php-basic-topic';
+$client = iggy_client();
+
+ensure_stream_and_topic($client, $stream, $topic);
+send_payloads($client, $stream, $topic, example_payloads('basic', 3));
+
+echo "Sent 3 PHP basic messages", PHP_EOL;
diff --git a/examples/php/getting-started/consumer.php 
b/examples/php/getting-started/consumer.php
new file mode 100644
index 000000000..fee42b9de
--- /dev/null
+++ b/examples/php/getting-started/consumer.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+declare(strict_types=1);
+
+require_once __DIR__ . '/../src/common.php';
+
+$stream = 'php-getting-started-stream';
+$topic = 'php-getting-started-topic';
+$client = iggy_client();
+
+ensure_stream_and_topic($client, $stream, $topic);
+print_polled_messages($client, $stream, $topic, 5);
diff --git a/examples/php/getting-started/producer.php 
b/examples/php/getting-started/producer.php
new file mode 100644
index 000000000..f0ab90486
--- /dev/null
+++ b/examples/php/getting-started/producer.php
@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+declare(strict_types=1);
+
+require_once __DIR__ . '/../src/common.php';
+
+$stream = 'php-getting-started-stream';
+$topic = 'php-getting-started-topic';
+$client = iggy_client();
+
+ensure_stream_and_topic($client, $stream, $topic);
+send_payloads($client, $stream, $topic, example_payloads('getting-started', 
5));
+
+echo "Sent 5 PHP getting-started messages", PHP_EOL;
diff --git a/examples/php/src/common.php b/examples/php/src/common.php
new file mode 100644
index 000000000..a14a3a02b
--- /dev/null
+++ b/examples/php/src/common.php
@@ -0,0 +1,91 @@
+<?php
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+declare(strict_types=1);
+
+use Iggy\Client;
+use Iggy\PollingStrategy;
+use Iggy\SendMessage;
+
+function iggy_connection_string(): string
+{
+    $configured = getenv('IGGY_CONNECTION_STRING');
+    if ($configured !== false && $configured !== '') {
+        return $configured;
+    }
+
+    $host = getenv('IGGY_HOST') ?: '127.0.0.1';
+    $port = getenv('IGGY_PORT') ?: '8090';
+    $username = rawurlencode(getenv('IGGY_USERNAME') ?: 'iggy');
+    $password = rawurlencode(getenv('IGGY_PASSWORD') ?: 'iggy');
+
+    return "iggy+tcp://{$username}:{$password}@{$host}:{$port}";
+}
+
+function iggy_client(): Client
+{
+    $client = Client::fromConnectionString(iggy_connection_string());
+    $client->connect();
+
+    return $client;
+}
+
+function ensure_stream_and_topic(Client $client, string $stream, string 
$topic): void
+{
+    if ($client->getStream($stream) === null) {
+        $client->createStream($stream);
+    }
+
+    if ($client->getTopic($stream, $topic) === null) {
+        $client->createTopic($stream, $topic, 1);
+    }
+}
+
+function send_payloads(Client $client, string $stream, string $topic, array 
$payloads): void
+{
+    $messages = array_map(
+        static fn (string $payload): SendMessage => new SendMessage($payload),
+        $payloads,
+    );
+
+    $client->sendMessages($stream, $topic, 0, $messages);
+}
+
+function print_polled_messages(Client $client, string $stream, string $topic, 
int $count): void
+{
+    $messages = $client->pollMessages($stream, $topic, 0, 
PollingStrategy::first(), $count, true);
+
+    if (count($messages) < $count) {
+        throw new RuntimeException("Expected {$count} messages, received " . 
count($messages));
+    }
+
+    foreach ($messages as $message) {
+        echo "Received message at offset {$message->offset()}: 
{$message->payload()}", PHP_EOL;
+    }
+}
+
+function example_payloads(string $prefix, int $count): array
+{
+    return array_map(
+        static fn (int $index): string => "{$prefix}-message-{$index}",
+        range(1, $count),
+    );
+}
diff --git a/scripts/run-examples-from-readme.sh 
b/scripts/run-examples-from-readme.sh
index 35c8dc9c6..8567c6d05 100755
--- a/scripts/run-examples-from-readme.sh
+++ b/scripts/run-examples-from-readme.sh
@@ -22,7 +22,7 @@ set -euo pipefail
 # Unified script to run SDK examples from README.md files.
 # Usage: ./scripts/run-examples-from-readme.sh [OPTIONS]
 #
-#   --language LANG   Language to test: rust|go|node|python|java|csharp 
(default: all)
+#   --language LANG   Language to test: rust|go|node|python|php|java|csharp 
(default: all)
 #   --target TARGET   Cargo target architecture for the server binary
 #   --skip-tls        Skip TLS example tests
 #
@@ -284,6 +284,47 @@ run_python_examples() {
     return "${EXAMPLES_EXIT_CODE}"
 }
 
+# shellcheck disable=SC2329
+run_php_examples() {
+    resolve_server_binary "${TARGET}"
+
+    local php_bin="${PHP:-php}"
+    if [ -z "${PHP_IGGY_EXTENSION:-}" ]; then
+        local extension_candidate
+        for extension_candidate in \
+            target/debug/libiggy_php.so \
+            target/debug/libiggy_php.dylib \
+            foreign/php/target/debug/libiggy_php.so \
+            foreign/php/target/debug/libiggy_php.dylib; do
+            if [ -f "${extension_candidate}" ]; then
+                PHP_IGGY_EXTENSION="$(pwd)/${extension_candidate}"
+                break
+            fi
+        done
+        export PHP_IGGY_EXTENSION
+    fi
+
+    if [ -z "${PHP_IGGY_EXTENSION:-}" ] || [ ! -f "${PHP_IGGY_EXTENSION}" ]; 
then
+        echo "Error: PHP extension not found. Build foreign/php first or set 
PHP_IGGY_EXTENSION."
+        exit 1
+    fi
+
+    TRANSFORM_COMMAND() {
+        local command="$1"
+        printf '%s\n' "${command/#php /\"${php_bin}\" }"
+    }
+
+    run_language_examples \
+        "PHP" \
+        "examples/php" \
+        "README.md" \
+        "^php " \
+        "" \
+        "" \
+        0 \
+        "--fresh"
+}
+
 # shellcheck disable=SC2329
 run_java_examples() {
     resolve_server_binary "${TARGET}"
@@ -355,16 +396,17 @@ case "${LANGUAGE}" in
     node)   run_one run_node_examples   "Node"   ;;
     go)     run_one run_go_examples     "Go"     ;;
     python) run_one run_python_examples "Python" ;;
+    php)    run_one run_php_examples    "PHP"    ;;
     java)   run_one run_java_examples   "Java"   ;;
     csharp) run_one run_csharp_examples "C#"     ;;
     all)
-        for lang in rust node go python java csharp; do
+        for lang in rust node go python php java csharp; do
             run_one "run_${lang}_examples" "${lang}"
         done
         ;;
     *)
         echo "Unknown language: ${LANGUAGE}"
-        echo "Supported: rust, node, go, python, java, csharp, all"
+        echo "Supported: rust, node, go, python, php, java, csharp, all"
         exit 1
         ;;
 esac

Reply via email to