detain / phlix-shared
Shared interfaces, DTOs, event names, and protocol types used by both phlix-server and phlix-hub. Composer-installable, PHP 8.3+, zero I/O by charter — bundled I/O is confined to the Arr/ namespace: the blocking CurlArrTransport default (CLI/test posture) and TrashGuidesProvider's own blocking strea
Requires
- php: ^8.3
- psr/clock: ^1.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/log: ^3.0
Requires (Dev)
- ext-curl: *
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.0
- squizlabs/php_codesniffer: ^3.10
- vimeo/psalm: ^5.0
Suggests
- ext-curl: Required only for the bundled blocking Arr\Transport\CurlArrTransport (CLI/test use). Event-loop (Workerman/Webman) consumers should inject an async ArrTransportInterface instead and do not need ext-curl.
- workerman/http-client: Event-loop (Workerman/Webman) consumers should inject a workerman/http-client-backed Arr\Transport\ArrTransportInterface so *arr API calls do not block the worker; the bundled CurlArrTransport is blocking and for CLI/test only.
Provides
None
Conflicts
None
Replaces
None
- dev-master
- v0.53.0
- v0.52.0
- v0.51.0
- v0.50.0
- v0.49.1
- v0.49.0
- v0.48.1
- v0.48.0
- v0.47.0
- v0.46.0
- v0.45.0
- v0.44.0
- v0.43.0
- v0.42.0
- v0.41.0
- v0.40.0
- v0.39.0
- v0.38.0
- v0.37.0
- v0.36.0
- v0.35.0
- v0.34.0
- v0.33.0
- v0.32.0
- v0.31.0
- v0.30.0
- v0.29.0
- v0.28.0
- v0.27.0
- v0.26.0
- v0.25.0
- v0.24.0
- v0.23.0
- v0.22.0
- 0.21.0
- 0.20.0
- v0.19.0
- v0.18.0
- v0.17.0
- v0.16.0
- v0.15.0
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.1
- v0.11.0
- v0.10.1
- v0.10.0
- v0.9.2
- v0.9.1
- v0.8.1
- v0.8.0
- v0.7.1
- v0.7.0
- 0.6.0
- 0.5.1
- v0.5.0
- v0.4.1
- v0.4.0
- v0.3.0
- v0.2.0
- v0.1.0
This package is auto-updated.
Last update: 2026-11-02 14:37:36 UTC
README
Shared interfaces, DTOs, event names, and protocol types used by both
detain/phlix-server (the media server)
and detain/phlix-hub (the multi-server hub).
Composer-installable, PHP 8.3+, zero I/O by charter — pure interfaces and
value objects, with one sanctioned exception: the Arr\* namespace ships
real (blocking cURL) *arr HTTP clients; see AGENTS.md.
Status
v0.52.0 (Phlix\Shared\Version::VERSION). schemas/server-settings.schema.json carries 84 keys (51 restart: true, 33 restart: false); schemas/hub-settings.schema.json carries 18 keys in lockstep with phlix-hub's HubSettingsRepository::ALLOWED_KEYS. Cumulative surface:
Phlix\Shared\Plugin\{LifecycleInterface, ConfigurableInterface, Manifest, ManifestType, ManifestValidationError, EventNameMap}Phlix\Shared\Events\{AbstractEvent, Playback\*, Library\*, Auth\*}— 12 event DTOs.Phlix\Shared\Auth\{JwtClaims, ProviderInterface, AuthResult, UserInfo}Phlix\Shared\Metadata\MetadataSourceInterface— the typed contract a metadata-source plugin implements (sourceName(),supportedMediaTypes(),search(),getDetails(),getImages()) so the server'sSourceRegistrycan register/deregister it on plugin enable/disable without the oldmethod_exists/FQCN-sniffing convention (0.15.0+).Phlix\Shared\Subtitle\{SubtitleSourceInterface, SubtitleCandidate, SubtitleFile, Exception\QuotaExceeded}— the subtitle analogue ofMetadataSourceInterface:getName(),getPriority(), thesearchByPath()/searchByHash()/searchByImdbId()fan-out returningSubtitleCandidatevalues, and the quota-consumingdownload()that may throwQuotaExceeded(0.42.0+).Phlix\Shared\Hub\{ClaimRequest, ClaimResponse, ServerInfoDto, HeartbeatDto, LibraryRef}Phlix\Shared\Relay\{RelayFrameType, RelayWireCodecInterface, RelayFrame}— channel-mux protocol (0.5+); plusRelay\{RelayHttpRequest, RelayHttpRequestHead, RelayHttpRequestChunk, RelayHttpRequestCodec}andRelay\{RelayHttpResponseHead, RelayHttpResponseChunk, RelayHttpResponseCodec}— HTTP-over-relay request/response envelopes +HEAD→BODY*→ENDchunk framing (0.10.0+).Phlix\Shared\Arr\{ArrClientInterface, ArrClientFactory, AbstractArrClient, BazarrClient, ProwlarrClient, RadarrClient, SonarrClient, SyncResult, SecretRedactor, TrashGuidesProvider}— *arr HTTP clients with their factory, shared base, sync value object, redactor and TrashGuides provider;Arr\Transport\{ArrTransportInterface, CurlArrTransport}is the pluggable wire layer underneath.Phlix\Shared\Security\Hmac— constant-time HMAC sign/verify primitive (webhooks, relay tokens).Phlix\Shared\Support\PayloadAssert— the presence/type assertion trait every wire DTO inHub\/Auth\parses through.Phlix\Shared\Schema\SchemaPaths— pure path resolver for the bundledschemas/files (0.7.0+).Phlix\Shared\Version— the singleVERSIONconstant consumers pin and report.
Bundled schemas
The package ships the canonical JSON files used by both phlix-server and the
admin SPA under schemas/ (resolve their absolute paths via the dedicated
Phlix\Shared\Schema\SchemaPaths accessors — serverSettings(), hubSettings(),
webhookEvents(), mediaItem(), libraryQuery() — and via SchemaPaths::dir()
for the files without a dedicated accessor, such as manifest.schema.json):
-
schemas/manifest.schema.json— JSON Schema (draft 2020-12) for plugin manifests, loaded at runtime byphlix-server'sPhlix\Plugins\Manifest\ManifestSchemavalidator (0.6.0+). Per-settinglabelanddescriptionare permitted, andinteger/booleanare accepted as aliases ofint/boolin the settingtypeenum (0.9.1+). An optional per-settingtier(standard|advanced) is accepted on plugin settings fields so aplugin.jsondeclaring it passes install-time manifest validation (0.43.0+). -
schemas/hub-settings.schema.json— JSON Schema (draft 2020-12) for the editable hub settings (/api/v1/me/hub-settings), resolved viaSchemaPaths::hubSettings()(0.22.0+). Its property keys must stay in lockstep withphlix-hub'sHubSettingsRepository::ALLOWED_KEYS— that constant is what the hub settings controller enumerates, and the schema supplies only the render metadata for those keys (0.24.0+). The lockstep is guarded from this side bytests/Schema/HubSettingsSchemaTest.php, whose drift test parses the LIVE constant out of a hub checkout — found via thePHLIX_HUB_REPOenv pointer (a broken pointer fails the run; it never silently skips) or aphlix-hubsibling directory of this checkout — and skips only when no hub checkout exists at all (shared CI takes that skip; the test's frozen mirror and the hub's own reverse guard cover the keys there). -
schemas/server-settings.schema.json— JSON Schema (draft 2020-12) for the editable server settings (/api/v1/admin/settings); phlix-server derives its writable allow-list from this schema and the admin SPA renders the settings form from it (0.7.0+). Extended with optional UI-metadata keywords (label,helpText,helpLinks,tier,enumLabels,optionHelp,secret,restart) so the admin settings UI can render per-option help text and split Standard vs Advanced options (0.22.0+). Their normative semantics — requiredness, shape, and the verified consumer effect of each keyword — are specified in-repo by the keyword-semantics table at the top oftests/Schema/SettingsSchemaAssertions.php, whose assertions are that table's executable enforcement; no external plan document is needed to author or review a key.Key-naming contract (0.25.0+). Every property key must be a dotted path that
Phlix\Admin\SettingsRepository::getDefault()can resolve: a LEADING RUN of segments names a config file under the consuming repo'sconfig/, and the remaining segments index into that file's returned array. The file part may span subdirectories — the longest matching file path wins, soscrobblers.trakt.client_idresolvesconfig/scrobblers/trakt.phpin preference to a same-named flat file. Every file segment is jailed to/^[A-Za-z0-9_-]+$/, so no key can escapeconfig/. A key that does not resolve renders an empty control and can never take effect, so no such key may be declared. Thetrakt.*keys deliberately use a FLAT prefix backed byphlix-server'sconfig/trakt.phpre-export shim rather than the nestedscrobblers.trakt.*form:trakt.*overrides are already persisted in liveserver_settingstables andTraktOperatorConfig::SETTING_KEY_MAPreads those exact keys, so renaming them would orphan real rows (0.25.0+). Addsmatching.noise_suffixes(array of strings; the admin-extensible match-title noise list — 0.13.0+) andmetadata.provider_priority(object: media type → ordered array of source names; defaultsmovie/series=["tmdb","imdb"],anime=["anidb","myanimelist","tvdb","fanart","local"])metadata.genres_mode(enumfirst|union, defaultfirst) for the per-media-type metadata source-priority editor (0.14.0+). Addsmetadata.overwrite_existing(boolean,group: metadata,tier: standard, 0.41.0+),subtitles.provider_priority(a single flat ordered array of subtitle-source names, default["opensubtitles"], read bySubtitleFetchService/SubtitleSourceRegistry::byPriority()— 0.44.0+), anddlna.allowed_cidrs(array, default[]) +dlna.restrict_to_lan(boolean, defaulttrue), bothtier: advanced/group: subsystem, read live byDlnaAllowlistMiddlewareto gate the unauthenticated DLNA browse/stream endpoints (0.45.0+). Addstranscoding.segment_format(enummpegts|fmp4,group: transcoding,tier: advanced,restart: false), which is what makes the segment container settable over the admin API — theenummirrors phlix-server'sEncodeSettings::SEGMENT_FORMATSand is read byEncodeSettings::segmentFormat()at encode time (0.49.0+); itsdefault,enumLabels,optionHelp,helpText, anddescriptionnow describefmp4as the shipped container andmpegtsas the supported rollback (0.49.1+).
restartflags (0.46.0–0.48.0 audit). Every one of the then-72 keys was traced to its consumer (the audit window held 72 — v0.46.0 = v0.47.0 = v0.48.0 = v0.48.1; the 73rd,transcoding.segment_format, arrived with the 0.49.0 addition named just above); keys added since each carry an explicit flag too. As of 0.52.0 the schema holds 84 keys: 51 carryrestart: trueand 33 carryrestart: false.restart: truemeans the admin Restart server control (a graceful SIGUSR2 reload that cycles workers, re-runsonWorkerStart, and rebuilds DI containers) — required for any value captured at worker start, container build, or route build. Therestart: falsekeys resolve throughSettingsRepository::getEffective()at use time and are genuinely live. -
schemas/webhook-events.json— data catalog of the wire-truth webhook event types for the admin SPA webhook picker. Note: this file is currently a subset — the server'sPhlix\Webhooks\WebhookEventCatalogand thephlix-uipicker deliberately lead it today (theuser.*events the server already emits are not yet named here; widening this catalog is owed in this package). Distinct from the plugin PSR-14 events inEventNameMap(0.7.0+). -
schemas/library-query.schema.json— JSON Schema (draft 2020-12) for the query parameters of the movie-list browse API (GET /api/v1/media); drivesItemRepository::query()and the admin SPA browse page (0.8.0+). Adds thelibraryId/parentId/topLevelscoping parameters for series→season→episode navigation (0.9.0+). -
schemas/media-item.schema.json— JSON Schema (draft 2020-12) for a single media item returned by the browse API; flattensmetadata_jsoninto stable top-level fields and always includesposter_url(0.8.0+). Thetypeenum gainsseasonalongsideparent_id,season_number,episode_number, andepisode_titlefor the series hierarchy (0.9.0+).
The PSR-14 dispatcher wiring (Tukio) and the schema validators stay in
phlix-server and consume this package via Composer.
Adding a server settings key (runbook)
The schemas are this repo's whole job; touching one means touching its test
locks in the same commit. tests/Schema/ServerSettingsSchemaTest.php holds
three locks, and every one of them fails loudly with guidance text —
this runbook is those steps written down before you hit them:
- Schema JSON. Add the property to
schemas/server-settings.schema.json. The key must be a dotted path resolvable by the server'sSettingsRepository::getDefault()(leading run = aconfig/file, longest file path wins, every file segment jailed to/^[A-Za-z0-9_-]+$/— see the Key-naming contract above). Give itlabel,helpText,group,tier, and an explicitrestart(true/false— the unset count is pinned at 0); mark credentialssecret: true. - Roster lock. Add the key's row to
ServerSettingsSchemaTest::propertyProvider()('key' => ['key', '<json-type>']) and bump theassertCount(N, $actual)intest_schema_has_exactly_the_expected_property_keys— the roster test asserts the schema declares exactly the documented keys, set-equality plus count. - Pin lock. Add the key's full-value row to
ServerSettingsSchemaTest::pinTable(): type / default / bounds / enum / restart / secret, each verified to reproduce deployed behavior before it ships (theschemaPinProvider()data-provider test checks the schema against this table, andtest_pin_table_covers_exactly_the_schema_propertiesset-equality refuses a schema key without a pin row — its throw message is the deliberate "add the rows deliberately" guidance). - Naming/keyword laws. The remaining pins (first-segment config-file name, forbidden key namespaces, secret-credential list, label/help presence, enum-option documentation, numeric bounds presence) run per key and per header; read their failure messages, do not widen them.
- Consumer rule. Keys are deleted through
CONSUMERLESS_KEY_DENYLIST(deletion record; a deny-listed key must not be re-added). A new key must therefore have a real consumer inphlix-serverreading the effective value — adding schema surface nothing reads is how key-count theater starts. - Hub keys differ. For
schemas/hub-settings.schema.json, the property keys must stay set-equal withphlix-hub'sHubSettingsRepository::ALLOWED_KEYS(see Bundled schemas above); the two-sided drift guard istests/Schema/HubSettingsSchemaTest.php(live via a hub checkout or thePHLIX_HUB_REPOpointer; CI runs the frozen mirror leg). - Verify + release.
./vendor/bin/phpunit,phpstan analyze, both phpcs legs,composer validate --strict— all green in the same commit. Release is lockstep:Version::VERSION= CHANGELOG heading = annotated git tag (AGENTS.md "Versioning"); consumers then bump their^0.xpin.
Requirements
- PHP
^8.3— CI-verified on PHP 8.3 and 8.4 (phpcs, phpstan and phpunit each run both legs; the psalm gate is 8.3-only because the toolchain's locked vimeo/psalm 5.x declares a~8.3.0ceiling); declared compatible below PHP 9.0. - Composer 2.x
psr/clock ^1.0psr/container ^2.0psr/event-dispatcher ^1.0psr/log ^3.0
psr/event-dispatcher is deliberately declarative: no class in src/
imports a PSR-14 interface — dispatching the plain event DTOs under
Phlix\Shared\Events\ is structurally valid PSR-14 usage — and the
require is kept as a consumer-convenience typing declaration so packages
wiring a dispatcher resolve the contract through this package's own
dependency set (keep-by-decision, not oversight; see
Phlix\Shared\Events\AbstractEvent).
The package has zero framework dependencies — no Workerman, no Monolog,
no Smarty. It is intended to be safely required by any PHP 8.3+ codebase,
with one caveat: the Arr\* HTTP clients are real network code (their
default transport needs ext-curl, or inject an ArrTransportInterface;
see AGENTS.md).
Installation
detain/phlix-shared is published on Packagist:
composer require detain/phlix-shared:^0.52
Related repositories
detain/phlix-server— the Phlix media server (consumes this package).detain/phlix-hub— the multi-server hub + reverse-tunnel relay.
Development
composer install ./vendor/bin/phpunit ./vendor/bin/phpstan analyze --no-progress ./vendor/bin/phpcs --standard=PSR12 src/ ./vendor/bin/phpcs --standard=phpcs-tests.xml ./vendor/bin/psalm --no-progress composer validate --strict php scripts/security-audit-check.php
CI enforces every line above, including the tests/ phpcs gate. The
composer scripts test, stan, cs, cs-tests and psalm alias the
vendor-binary lines.
⚠ The last step replaces composer audit --no-dev. Never audit with a
development-dependency exclusion: --no-dev drops every require-dev package
from the audited set, and it hid CVE-2026-67434 (HIGH, OS command injection) in
squizlabs/php_codesniffer — a require-dev package — so CI reported SUCCESS
against a vulnerable lock. scripts/security-audit-check.php audits the whole
lock, labels each finding [require] / [require-dev], and prints the number of
packages it examined so a truncated audit cannot pass as a clean one. See
AGENTS.md for the full policy.
The phpunit job in .github/workflows/ci.yml uploads
./coverage.xml to both Codecov and Codacy.
License
MIT — see LICENSE.