ondewo / nlu-client-php
ONDEWO NLU (Natural Language Understanding) gRPC client for PHP, generated from the ondewo-nlu-api protocol buffer definitions
Requires
- php: >=8.1.0
- ext-grpc: *
- google/protobuf: 4.33.6
- grpc/grpc: 1.82.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-14 11:08:06 UTC
README
|
|
|
ONDEWO NLU Client PHP Library
This library is the PHP gRPC client for the ONDEWO NLU (Natural Language Understanding) server.
There is no hand-written transport layer in this repository. The entire client surface — messages, enums and
one <Service>Client stub per gRPC service — is generated from the protocol buffer definitions of the
ondewo-nlu-api repository by the
ONDEWO proto compiler, which is vendored here as a git
submodule and pinned to a release tag. The generated stubs are committed — the repository root is the
composer package, Packagist serves the tree of a git tag verbatim and composer has no build step, so what is
committed here is exactly what a consumer receives.
The only hand-written PHP is the bearer-token authentication surface in auth/, which turns a token into the
$opts array a generated stub is constructed with.
Requirements
- PHP >= 8.1
- The grpc PHP extension (
ext-grpc) — every generated<Service>Clientextends\Grpc\BaseStub - The bcmath PHP extension (
ext-bcmath) — only to parse integers out of the JSON wire format;google/protobufmerely suggests it, somergeFromJsonString()on a message with an int field fails without it - Composer 2.x
- Docker — only to regenerate the stubs, never to use the client
Installing the extension:
# Debian / Ubuntu sudo apt-get install -y php-grpc php-bcmath # or, from source sudo pecl install grpc
Installation
The client is published on Packagist as
ondewo/nlu-client-php and is installed like any other composer package:
composer require ondewo/nlu-client-php
The client version tracks the ONDEWO NLU API in major and minor version, so pin the minor series that matches the server you talk to:
composer require ondewo/nlu-client-php:^7.1
or, in composer.json:
{
"require": {
"ondewo/nlu-client-php": "^7.1"
}
}
Two things a consumer has to know:
ext-grpcis a hard requirement, not an optional extra — every generated<Service>Clientextends\Grpc\BaseStub, so composer refuses to install the package without it. Install it first (see Requirements above);composer require ondewo/nlu-client-php --ignore-platform-req=ext-grpcwill resolve, but the client will fatal at runtime.ext-bcmathis not declared —google/protobufonly suggests it — but its pure-PHP JSON parser range-checks integers withbccomp(), so install it too if you read or write the JSON wire format.- There is no build step. Packagist serves the tree of a git tag verbatim and the generated stubs are
committed, so the package a consumer downloads is byte-for-byte the tagged repository: after
composer require,vendor/ondewo/nlu-client-php/src/already holds every stub and the autoloader is generated by composer itself. Nothing needs docker, protoc or theondewo-nlu-apisubmodule.
Everything is then reachable through composer's autoloader:
require __DIR__ . '/vendor/autoload.php'; use Ondewo\Nlu\AgentsClient; // generated, from src/ use Ondewo\Nlu\Auth\BearerTokenAuthenticator; // hand-written, from auth/
To work on the client itself:
git clone --recurse-submodules git@github.com:ondewo/ondewo-nlu-client-php.git
cd ondewo-nlu-client-php
make setup_developer_environment_locally
make help lists every documented target; make makefile_chapters lists the Makefile's sections.
Repository structure
.
├── ondewo-nlu-api <----- submodule: the .proto definitions (ondewo/ = the services, google/ = imports)
├── ondewo-proto-compiler <----- submodule: the compiler images, pinned to tags/5.15.1
├── auth <----- HAND-WRITTEN sources (bearer token authenticator)
├── src <----- GENERATED stubs, committed - compiler-owned, wiped on every generation run
│ ├── GPBMetadata <----- descriptor bootstrap, one class per .proto
│ └── Ondewo <----- messages, enums and the <Service>Client stubs
├── tests <----- PHPUnit suite (not part of the published classmap)
├── tools <----- dev-only composer project: PHPUnit + the coverage gate
├── vendor <----- composer dependencies (gitignored)
├── composer.json <----- the package manifest; MERGED with the compiler defaults on every run
├── phpunit.xml.dist <----- test suite + coverage scope (auth/ only)
└── Makefile <----- build, test and release automation
Three rules follow from that layout and matter more than anything else in this file:
- Never put hand-written PHP in
src/. It is deleted and rewritten on every generation run. Hand-written code belongs inauth/at the repository root — the compiler image detects that directory and adds it to the shipped autoloader's classmap itself. - Never pin
google/protobuforgrpc/grpcincomposer.jsonto anything other than the versions the compiler image ships. The image resolves the merged manifest offline from a cache it pre-warmed at image-build time; a pin outside that cache fails the generation run. - Never add
require-devto the rootcomposer.json.composer update --no-devstill resolves dev requirements in order to write a lock file, so a single entry there makes that same offline resolution fail. Dev tooling lives in its own composer project undertools/— see tools/README.md.
Regenerating the stubs
make build
That is the whole flow, and it is: check out the pinned submodules → build the ondewo-php-proto-compiler:latest
image from the submodule → run it over the protos → hand the generated files back to your user → write the
client version into composer.json.
The generation step on its own is a single container run:
docker run --rm \ -v $(pwd):/input-volume \ -v $(pwd):/output-volume \ ondewo-php-proto-compiler:latest ondewo-nlu-api ondewo
- The two positional arguments are
<relative_protos_dir> <target_subdir>: the proto root inside the input volume (which becomes protoc's-Iroot), and the sub-directory to scope generation to.ondewokeeps the vendoredgoogle/tree out of the entry set while the image's dependency resolver still pulls in the google protos that are actually imported. - There is no
-it. It breaks every non-interactive caller withcannot attach stdin to a TTY-enabled container because stdin is not a terminal. - Input and output volume are both the repository root. The image copies the input volume into an internal
temporary directory and compiles there, so the mounted input is never mutated; it then writes
composer.json,composer.lock,src/andvendor/back here, wiping its ownsrc/andvendor/first so a renamed or deleted proto leaves no orphaned stub behind. - The container runs as root, so the files it writes are root-owned.
make buildchases that withmake fix_generated_ownership; run it by hand if you invoke docker directly.
To poke around inside the image, and only there, -it is correct:
docker run -it --entrypoint /bin/bash \ -v $(pwd):/input-volume \ -v $(pwd):/output-volume \ ondewo-php-proto-compiler:latest
Usage
<?php require __DIR__ . '/vendor/autoload.php'; use Grpc\ChannelCredentials; use Ondewo\Nlu\AgentsClient; use Ondewo\Nlu\AgentView; use Ondewo\Nlu\Auth\BearerTokenAuthenticator; use Ondewo\Nlu\ListAgentsRequest; // The PHP namespace is protoc's UpperCamel form of the proto package: // `package ondewo.nlu;` becomes `Ondewo\Nlu`, and a service `Agents` becomes // `AgentsClient` (grpc_php_plugin's default class suffix). Browse src/Ondewo/Nlu for // the services and messages your pinned API version actually declares. // // BearerTokenAuthenticator is the hand-written half: it builds the `$opts` array and // stamps `authorization: Bearer <token>` onto the metadata of every call. $auth = new BearerTokenAuthenticator(getenv('ONDEWO_TOKEN')); $client = new AgentsClient( getenv('ONDEWO_NLU_HOST') ?: 'localhost:50055', $auth->channelOptions(ChannelCredentials::createSsl()) ); $request = new ListAgentsRequest(); $request->setAgentView(AgentView::AGENT_VIEW_SHALLOW); [$response, $status] = $client->ListAgents($request)->wait(); if ($status->code !== \Grpc\STATUS_OK) { throw new RuntimeException("gRPC call failed ({$status->code}): {$status->details}"); } echo $response->serializeToJsonString(), PHP_EOL;
For an insecure channel against a local server, call $auth->channelOptions() with no argument — null
credentials is exactly what ChannelCredentials::createInsecure() returns.
Testing
make ci # what GitHub Actions runs: no submodules, no docker make test # the same, plus `make check_build` against the ondewo-nlu-api submodule
make ci is composer validate → the credential-free Packagist dry run
→ php -l over the hand-written sources → PHPUnit with coverage → the coverage threshold gate. It runs on
PHP 8.1 and 8.4 in GitHub Actions, against the committed stubs: no docker image is built and no proto
compiler runs there.
What the suite actually asserts:
| Test | What it would catch |
|---|---|
tests/Generated/GeneratedCodeTest.php |
A stub that does not load, a GPBMetadata descriptor whose initOnce() chain has a missing transitive import, a service whose client class was never generated, an empty src/ |
tests/Generated/MessageSerializationTest.php |
A field that never reaches the wire, a proto3 optional field that drops its zero value, a moved enum zero constant, a broken JSON mapping (including the integer path, which needs ext-bcmath) |
tests/Generated/ServiceClientTest.php |
A stub that cannot be constructed, a missing or re-shaped RPC method, a streaming RPC generated as a unary one |
tests/Auth/BearerTokenAuthenticatorTest.php |
Any regression in the hand-written auth surface |
Coverage is reported for the hand-written code only — phpunit.xml.dist's <source> is auth/, and
make coverage fails below COVERAGE_MIN (100%). The generated stubs are machine output and are deliberately
outside the metric, but they are not outside the tests: every one of the committed classes is loaded and every
descriptor initialised by GeneratedCodeTest.
Versioning and releasing
ONDEWO_NLU_VERSION at the top of the Makefile is the single source of truth and must match
the ONDEWO NLU API in major and minor version. make update_composer_version propagates it into
composer.json; never edit that field by hand.
# bump ONDEWO_NLU_VERSION and the submodule pins, add a RELEASE.md entry, then:
make ondewo_release
ondewo_release checks that the release branch and tag are still free, fetches the credentials from the
ondewo-devops-accounts repository and runs the release: build, commit, release branch, release tag, the
GitHub release (whose notes are sliced out of RELEASE.md by the version heading) and finally make publish.
Publishing to Packagist
Packagist takes no upload. There is no twine upload and no npm publish equivalent: Packagist reads the
git tags of this repository off GitHub and serves each tagged tree as a version. So "publishing" is two things,
and only the second one is a command:
- the git tag —
make create_release_tagpushes it, and that tag is the artifact; - a ping that tells Packagist to crawl the repository now instead of at its next scheduled pass —
make publish.
make publish # validate the package, then POST to https://packagist.org/api/update-package
make publish refuses to run without credentials, then runs the full dry run, then pings the API and fails on
anything but an HTTP 200 carrying {"status":"success"}. It is wired into make release, so a normal
make ondewo_release needs no extra step.
One-time setup (a human, once per package)
None of the automation can do these; they need a browser and ownership of the ONDEWO organisation.
- Submit the package once. Log in to packagist.org with the ONDEWO account and
use Submit with the repository URL
https://github.com/ondewo/ondewo-nlu-client-php. Packagist readscomposer.jsonand claims the vendor namespaceondewo/for that account. Until this is done the update API answers 404 for the package andmake publishfails — deliberately. - Install the GitHub service hook (optional but recommended) so a tag push updates Packagist even when a
release is made by hand: on Packagist the package page offers the hook URL and token, or use GitHub
Settings → Webhooks with payload URL
https://packagist.org/api/github?username=<PACKAGIST_USERNAME>, content typeapplication/json, secret = the Packagist API token, event Just the push event. The hook andmake publishdo the same job; having both is harmless, having neither means a release is only visible after Packagist's own crawl. - Store the credentials — see the table below.
Credentials
| Name | What it is | Where it lives |
|---|---|---|
PACKAGIST_USERNAME |
the Packagist login name that owns the ondewo/ vendor namespace |
ondewo-devops-accounts → account_packagist.env, and the GitHub repository secret of the same name |
PACKAGIST_API_TOKEN |
that account's API token, from packagist.org/profile → Show API token | ondewo-devops-accounts → account_packagist.env, and the GitHub repository secret of the same name |
GITHUB_GH_TOKEN |
the token gh release create authenticates with |
ondewo-devops-accounts → account_github.env (already in use) |
Both Packagist variables default to an ENTER_HERE_YOUR_… placeholder in the Makefile and are only ever
supplied at runtime — make ondewo_release reads them out of the devops-accounts clone, the release workflow
out of GitHub secrets. Every recipe that carries one is @-prefixed so it never reaches a build log, and
make TEST prints <set> / <unset> rather than the value.
What is verified without credentials
make packagist_dry_run is the credential-free half of make publish and runs in CI on every push
(.github/workflows/ci.yml, and make ci locally):
| Check | What it would catch |
|---|---|
composer validate |
a composer.json Packagist cannot parse |
composer_validate_strict |
any new strict-mode warning — the three deliberate ones (the version field and the two exact google/protobuf / grpc/grpc pins) are enumerated in the Makefile and allowed, everything else fails |
check_version_agreement |
ONDEWO_NLU_VERSION, composer.json's name and version, the RELEASE.md entry and — on a tag — the tag name drifting apart. A tag that disagrees with the version field makes Packagist publish the tree under a version nobody tagged |
check_packagist_payload |
the update request pointing at the wrong repository — the API identifies the package by its VCS url, not by its composer name |
.github/workflows/release.yml runs on a X.Y.Z tag push and does the same checks plus the full test suite
before it pings the API. If either secret is missing it fails in its first step with an explicit
::error:: naming the secret, rather than posting an unauthenticated request and reporting success.
See RELEASE.md for the release history and CONTRIBUTING.md for how to contribute.
License
Apache License 2.0 — see LICENSE.