Download the PHP package spinningcatstudios/lingara without Composer
On this page you can find all versions of the php package spinningcatstudios/lingara. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download spinningcatstudios/lingara
More information about spinningcatstudios/lingara
Files in spinningcatstudios/lingara
Package lingara
Short Description The official PHP library for the Lingara API: PSR-18 calls, foreach event streams, and OAuth client credentials.
License MIT
Homepage https://getlingara.com
Informations about the package lingara
spinningcatstudios/lingara
The official PHP library for the Lingara API. It
requires PSR interfaces only, so it works with the HTTP client your
application already has, and it streams generated vocabulary, lesson plans
and tutor replies as foreach loops.
PHP 8.2 or newer.
Install
symfony/http-client with nyholm/psr7 is the recommended HTTP stack.
guzzlehttp/guzzle 7 works too, with the allow_url_fopen ini setting on
(see HTTP clients).
Quick start
Omit both credentials for a client that calls only the four public
operations: getOpenApiDocument, getAsyncApiDocument, listApiVersions
and getApiVersion.
Every option is a named argument of new Client(...): authMethod,
scopes, tokenSource, tokenCache, baseUrl, tokenUrl, version,
onDeprecation, logger, maxAttempts, retryAfterCap,
streamIdleTimeout, tokenRequestTimeout, userAgentSuffix, clock,
sleeper, http and tailMaxFailures. Durations are float seconds.
Streams
A stream method sends its request and reads the response headers before it
returns, so a refusal (a 401 after its one retry, a 429, any other
non-2xx) is thrown by the call. It returns an EventStream, which you
iterate once; an in-stream failure, or the stream's error event, is
thrown from inside the foreach.
Leaving the loop by any path (break, return, an exception) closes the
connection. A stream you never iterate is closed when its last reference
goes, which in a long-lived process (a queue worker, RoadRunner) may be
later than you think, so close it yourself when your code may not reach the
loop:
Webhooks and events
Lingara tells your game when something happens (a lesson plan is ready, a
usage threshold is reached) through three doors that carry one envelope:
a signed webhook, a feed you page through, and a live tail. Each event is a
Lingara\Events\Event: one class per type under Lingara\Events\Generated\
(LessonPlanReady, LessonPlanFailed, …), each with id, type,
createdAt, apiVersion, subject and a typed data.
Verify the raw body first. Webhook checks the Standard Webhooks
signature against your lgr_whsec_… secret, then parses the body. Pass the
body exactly as it arrived (file_get_contents('php://input'), or
$request->getContent() in Symfony and Laravel), never a decoded and
re-encoded one, which no longer matches its signature. The headers are an
array, such as getallheaders(), or any PSR-7 request:
VerificationException is deliberately not a LingaraException, so a
catch-all around your API calls never swallows a forged delivery. Answer
2xx fast and do the work afterwards, and deduplicate by $event->id:
delivery is at least once and unordered. An event type newer than this
library arrives as UnknownEvent, with its data as decoded JSON:
acknowledge it like any other, or Lingara keeps redelivering it for about a
day, and log it, or you lose it. verifySignature() checks the signature
alone, for a signed body that is not an event.
The feed. events() walks every page after a cursor and stops at the
end; it never sleeps or polls. Save cursor() and pass it next time.
Without a cursor it starts from now, or from the oldest retained event with
start: 'oldest'. A cursor older than the 30-day window is an
ApiException with errorCode() cursor_expired: start again without one.
listEvents() is the single page it is built on.
The tail. tailEvents() streams events live and reconnects after every
ending, resuming from its cursor(), so it never ends on its own: leave the
foreach to stop it. It throws after tailMaxFailures (8) failed
reconnects in a row, about 90 seconds of backoff; catch that and restart
from cursor() to ride out a longer outage. The feed's cursor and the
tail's are one token, so tailEvents(cursor: $feed->cursor()) takes over
from a catch-up with no gap. streamEvents() is the one connection it is
built on.
Sending. sendEvent() tells Lingara what happened in your game:
Each call carries an Idempotency-Key: a fresh UUIDv4 unless you pass
idempotencyKey:, and the same one on every retry of that call. Pass your
own when your game may resend after a crash, because a generated key is gone
once the call returns. A reused key returns the first answer, whatever the
body, so never reuse one for a different event. Only
reaction.plan_status == generating promises a lesson_plan.ready or
lesson_plan.failed; a partial or complete plan is readable now, though
an event for it may still arrive.
Pin the version. data is rendered at your client's pinned API
version, and this library's models were generated for
Lingara\Version::GENERATED_FOR_VERSION: pin your OAuth client to that
version.
HTTP clients
PSR-18 does not say whether a response body streams, and it has no
per-request timeouts. So the library builds the two clients it has proven,
and lets you bring any other on your own terms. Pass one as http::
http: |
Events arrive as sent | streamIdleTimeout |
tokenRequestTimeout |
|---|---|---|---|
HttpStack::detect(), the default |
Symfony if installed, else Guzzle | enforced | enforced |
HttpStack::symfony() (symfony/http-client + nyholm/psr7) |
yes | enforced | enforced |
HttpStack::guzzle() (Guzzle 7, allow_url_fopen on) |
yes, within 0.1 s | enforced | enforced |
HttpStack::custom($client, $requestFactory, $streamFactory) |
only if your client streams | only if your client returns from a stalled read | not enforced |
With custom() the library uses your client exactly as given, for both the
API and the token endpoint. PHP cannot interrupt a blocking sendRequest()
or read() from inside the process, so streaming and both timeouts are
then your client's. A default-configured Guzzle client downloads the whole
body before sendRequest() returns: every event arrives at once, when the
stream ends, and a held stream never returns. Guzzle streams only when the
client is built with ['stream' => true] and allow_url_fopen is on.
The library does not support clients that suspend a fiber inside
sendRequest() (AMPHP or Revolt bridges) or Swoole's coroutine hooks.
Tokens and PHP-FPM
PHP-FPM discards the object graph after every web request, so an in-memory
token cache mints a token per request, and the token endpoint allows 60
mints per hour per client. Pass any PSR-16 cache as tokenCache and every
worker shares one token until shortly before it expires. The cost is that
an access token sits in your cache store. The cache is an optimisation,
never a failure: a backend that is down is a miss, and the call goes on.
The client secret and every token are kept out of var_dump, print_r,
var_export, json_encode and error messages. exposeSecret() is the one
accessor that returns a raw value.
Errors
Every failure a call throws implements Lingara\Exception\LingaraException:
| Class | When |
|---|---|
ApiException |
an API refusal, or a stream's error event (then status() is 200) |
OAuthException |
the token endpoint refused the credentials or the scopes |
MaintenanceException |
a plain-text 503: the API is under maintenance |
TransportException |
no usable answer; kind() says why: Connect, Tls, Reset, Timeout, StreamEndedEarly, MalformedResponse, MalformedEvent |
A 429 or 503 with a Retry-After of at most retryAfterCap (60 s) is
retried up to maxAttempts (3) times in all.
Versions and deprecation
Pin an API version with version:. When a response comes from a deprecated
version, onDeprecation receives a DeprecationNotice; with no hook, one
warning per version goes to logger, a PSR-3 logger that defaults to
error_log().
Testing seams
clock (a PSR-20 clock) and sleeper (callable(float $seconds): void)
exist for tests: the clock is read for token freshness and HTTP-date
Retry-After values, and the sleeper is handed every Retry-After wait and
every tail reconnect delay. Webhook takes a clock as its last argument too.
Generated code
src/Model/ and src/ObjectSerializer.php are generated by
openapi-generator, and src/Stream/, src/Events/Generated/,
src/Internal/Operations.php and src/Version.php by this package's own
generator. They are never edited by hand. The models, the stream and event
classes and InboundEvent are API; ObjectSerializer and
Lingara\Internal\ are not.
The contract
Every official Lingara library keeps one contract,
conformance/CONTRACT.md,
and passes its conformance cases on both built HTTP stacks. Where this
library meets a rule only on those stacks, the table above says so.
Licence
MIT.
All versions of lingara with dependencies
psr/clock Version ^1.0
psr/http-client Version ^1.0
psr/http-factory Version ^1.0
psr/http-message Version ^1.1 || ^2.0
psr/log Version ^1.1 || ^2.0 || ^3.0
psr/simple-cache Version ^2.0 || ^3.0