Flyokai¶
Async PHP application framework on AMPHP 3.x and Revolt — bootstrap lifecycle, HTTP server, CLI, async database drivers, OAuth, ACL, indexer, declarative schema, DTO toolkit, and a DI container designed for module composition.
Flyokai is a metapackage. The framework itself lives in vendor/flyokai/* — every module ships its own README.md, CLAUDE.md, and AGENTS.md. This package is the central place to learn the system: a quickstart, the architecture overview, the module catalog, and pointers into the rest of the docs.
Why Flyokai¶
- Cooperative async, end-to-end. HTTP, MySQL (PDO and MySQLi), CSV, OpenSearch, channels — none of them block.
- DI you can extend across modules. Register routes, CLI commands, ACL resources, indexers, setup steps, repositories without modifying upstream code.
- DTOs that respect both worlds.
Solidfor strict, immutable domain objects;Draft/GreyDatafor flexible inputs and dynamic schemas. - Schema as code. Tag a Solid DTO with
#[Table]; the schema framework reconciles MySQL on every install/upgrade. - Search as code. Tag the same DTO with
#[ForeignKey]; criteria queries auto-discover joins.
Quick start¶
Install¶
flyokai/flyokai is a metapackage — it provides the central docs and ties the framework modules together, but it is not a project skeleton. Bootstrap a new project from one of the editions:
# HTTP / JSON web API
composer create-project flyokai/webapi-edition my-app dev-dev
# Socket-based async data service (extends webapi-edition)
composer create-project flyokai/data-service-edition my-service dev-dev
cd my-app
If you want the Magento DTOs / cache backend on top of either edition, just composer require flyokai/magento-dto flyokai/magento-amp-mate afterwards. Or composer require flyokai/flyokai to pull every framework module transitively.
First-time setup¶
vendor/bin/flyok-setup install \
--db-host=localhost \
--db-user=app \
--db-pass=secret \
--db-name=app \
--base-url=http://localhost:8080
This deploys bin/flyok-setup, bin/flyok-console, bin/flyok-cluster, runs all DB setup steps, and reconciles the schema from #[Table]-tagged DTOs.
Subsequent operations¶
# Re-run setup (deploys + db-schema reconcile)
php bin/flyok-setup upgrade
# CLI commands
php bin/flyok-console acl:role:create --name admin '[]'
php bin/flyok-console user:create --uname=admin --email=admin@example.com --role=admin --pass=secret123
php bin/flyok-console oauth:client:create admin client_credentials
# Cluster
php bin/flyok-cluster start
php bin/flyok-cluster stop
php bin/flyok-cluster restart
# Reindex
php bin/flyok-console indexer:reindex
Why two
flyok-setups? Seedocs/cli.md. Initial install uses the vendor script; everything afterwards uses the deployed script (which has absolute paths and the production bootstrap baked in).
Architecture at a glance¶
Application types¶
| Type | Purpose |
|---|---|
Setup |
First-install / upgrade |
Cluster |
Coordinator process |
Worker |
Worker process — serves HTTP and listens for cluster messages (extends Web) |
Web |
Plain HTTP server |
Task |
One-off background tasks |
Cli |
Console commands |
Bootstrap lifecycle¶
Module composer.json autoload calls Registry::addModule()
│
▼
RootBootstrap(ApplicationType)
│
├── Init phase — every module's init() registers DI config files
├── Build phase — Amp Injector Application built from merged definitions
└── Bootstrap — every module's bootstrap() runs against the live container
HTTP flow¶
Data-service flow¶
Client → Socket → OAuth Handshake → StreamChannel → Dispatcher → Router → Middleware → Handler → Response
Database layer¶
Application Config
│
▼
ConnectionPool (Amp / AsyncPdo / AsyncMysqli / TwoLevel)
│
▼
Laminas Adapter
│
▼
Async Driver (suspends fibers, never blocks the loop)
Data pipeline (used by indexer + bulk-import workflows)¶
DataSource
│
▼
BatchProcessor
│
▼
Amplifier Pipeline (Prepare → Load → Validate → Diff → Save)
│
▼
Bulk DB Operations (InsertOnDuplicate, ID resolvers)
Core concepts¶
DI (flyokai/amphp-injector)¶
use Amp\Injector\Application;
use Amp\Injector\Definitions;
use Amp\Injector\Injector;
use function Amp\Injector\{any, arguments, names, object, singleton, value};
$definitions = (new Definitions())
->with(singleton(object(MyService::class, arguments(names()
->with('config', value(['key' => 'val']))
))), 'my_service');
$application = new Application(new Injector(any()), $definitions, 'my-app');
$application->start();
$svc = $application->getContainer()->get('my_service');
- Definition helpers —
singleton(),object(),value(),factory(),injectableFactory(),compositionFactory(),compositionItem() - Weavers —
names(),types(),runtimeTypes(),automaticTypes(),any() - Attributes —
#[ServiceParameter],#[SharedParameter],#[PrivateParameter],#[FactoryParameter(Class)] - Compositions —
CompositionOrderedwithbefore/after/depends
See the in-framework guide: docs/dependency-injection.md.
DTOs (flyokai/data-mate)¶
| Marker | Mutability | Construction | Use it for |
|---|---|---|---|
Solid |
Immutable, public readonly |
Constructor, validated | Domain objects |
Draft |
Mutable, flexible | Same DTO class as Solid but relaxed | Input staging |
GreyData |
Dynamic key/value | $data array |
API payloads, third-party blobs |
fromArray() — Valinor-flexible. cloneWith() — constructor-strict.
Declarative schema (flyokai/db-schema)¶
#[Table(name: 'flyok_user', alias: 'user')]
#[ForeignKey(name: 'fk_user_role', columns: ['role_id'], referencesDto: RoleSolid::class, referencesColumns: ['role_id'])]
final class UserSolid implements Solid {
public function __construct(
#[PrimaryKey(autoIncrement: true)] public readonly int $userId,
#[Column(length: 64)] public readonly string $username,
public readonly int $roleId,
) {}
}
bin/flyok-setup upgrade reconciles the live database with the declared model on every run. Drops are opt-in (FLYOK_DB_SCHEMA_ALLOW_DESTRUCTIVE=1).
Search criteria (flyokai/search-criteria)¶
$result = $userRepo->getList([
'AND' => [
['field' => 'status', 'op' => 'eq', 'value' => 'active'],
['field' => 'role.name', 'op' => 'in', 'value' => ['admin', 'editor']],
],
'sort' => [['field' => 'created', 'direction' => 'desc']],
'limit' => 20,
'withTotal' => true,
]);
role.name auto-discovers the FK and emits an INNER JOIN.
Async never blocks¶
// ❌ Don't do this in a handler
sleep(1);
// ✅ Do this
Amp\delay(1);
// ✅ Concurrent work
$futures = [Amp\async($a), Amp\async($b), Amp\async($c)];
$results = Amp\Future\await($futures);
Module catalog¶
Each module has a full README.md linked below. Internal "agent knowledge" is in AGENTS.md next to the README.
Foundation¶
| Module | Purpose |
|---|---|
flyokai/data-mate |
Base Dto interface, Solid/Draft/GreyData, identity, enums, collections |
flyokai/composition |
Topological sort for before/after/depends declarations |
flyokai/generic |
State<T>, Builder<T>, Tuner<T>, Execution<T> patterns |
flyokai/misc |
CryptKey, ProfilerFacade, PsrServerAdapter, SharedSequence |
flyokai/db-schema |
Attribute-driven MySQL schema (#[Table], #[Column], #[ForeignKey], …) |
flyokai/search-criteria |
Declarative JSON criteria with auto-join discovery |
DI & application¶
| Module | Purpose |
|---|---|
flyokai/amphp-injector |
DI container with weavers, compositions, lifecycle |
flyokai/application |
Bootstrap lifecycle, HTTP server, CLI, ACL, DB pools |
flyokai/revolt-event-loop |
Fork of revolt/event-loop adding onMysqli() |
Async infrastructure¶
| Module | Purpose |
|---|---|
flyokai/amp-mate |
Filesystem helpers, ampFlock |
flyokai/amp-channel-dispatcher |
Request/response RPC over Amp channels |
flyokai/amp-data-pipeline |
Concurrent data processing — sources, processors, batching, multicast |
flyokai/amp-csv-reader |
Streaming CSV parser |
flyokai/amp-opensearch |
Async adapter for the opensearch-php SDK |
Database¶
| Module | Purpose |
|---|---|
flyokai/laminas-db |
Fork of laminas/laminas-db — sync foundation |
flyokai/laminas-db-driver-amp |
Native AMPHP MySQL driver |
flyokai/laminas-db-driver-async |
AsyncPdo (worker pool) + AsyncMysqli (MYSQLI_ASYNC) |
flyokai/laminas-db-bulk-update |
INSERT … ON DUPLICATE KEY UPDATE, ID resolvers, range chunking |
flyokai/zend-db-sql-insertmultiple |
Multi-row INSERT VALUES builder |
Service / console / auth¶
| Module | Purpose |
|---|---|
flyokai/data-service |
Socket-based async service layer |
flyokai/data-service-message |
Inter-service request/response DTOs |
flyokai/symfony-console |
Async-friendly Symfony Console with required args / filesystem validators |
flyokai/user |
User auth — DTOs, repository, management |
flyokai/oauth-server |
OAuth 2.0 (league/oauth2-server) — password, client credentials, refresh |
flyokai/indexer |
Search/indexing framework with atomic table swap |
Magento¶
| Module | Purpose |
|---|---|
flyokai/magento-dto |
Magento DTOs — catalog, sales, inventory, scope, system config |
flyokai/magento-amp-mate |
Async Magento cache backend (CmCacheBackendFile) |
Documentation¶
Framework-level documents in this package's docs/:
| Doc | Description |
|---|---|
docs/getting-started.md |
30-minute walkthrough — install → smoke test → first DTO → first endpoint |
docs/architecture.md |
Master diagrams: bootstrap lifecycle, HTTP / data-service flow, DB layer, schema, search-criteria, DI |
docs/dependency-injection.md |
DI config file structure, build flow, services vs definitions, arguments merging, composition pattern |
docs/cli.md |
Which binary to use when (vendor/bin/flyok-setup vs bin/flyok-setup vs bin/flyok-console vs bin/flyok-cluster) |
docs/setup-and-deploy.md |
Install vs upgrade phases, deploy assets, non-interactive install, schema reconcile, production deploy |
docs/asynchrony.md |
The "never block" rules — blocking → async conversion table, concurrency idioms, suspension API |
docs/troubleshooting.md |
Symptoms grouped by layer with the fastest fix |
docs/upgrading.md |
Bumping versions, edition migration, breaking-change protocol |
docs/scaffolding.md |
Skills + commands that scaffold recurring tasks |
docs/glossary.md |
Every flyokai-specific term in one page, linked back to the relevant module |
docs/module-naming.md |
Naming conventions for new modules and editions |
docs/composer-repositories.md |
Inline-repos pain and the recommended Satis migration |
Development skills¶
In .agents/skills/ (also synced to .claude/ and project-root .agents/). Each skill describes prerequisites, scaffolding, conventions, and reference examples for a common task. See docs/scaffolding.md for invocation patterns.
| Skill | Purpose |
|---|---|
new-module |
Scaffold a complete flyokai module with bootstrap lifecycle |
new-endpoint |
Add HTTP route + controller + auth guard |
new-dto |
Create a DTO following data-mate conventions |
new-cli-command |
Add an async Symfony Console command |
new-request-handler |
Add a data-service socket request handler |
Module-specific skills:
- flyokai/db-schema/.agents/skills/annotate-dto-schema — tag a Solid DTO with #[Table] and friends
- flyokai/search-criteria/.agents/skills/wire-searchable-repository — add getList() / massUpdate() to an existing repository
Module composition cheatsheet¶
| What you want to extend | Where to register |
|---|---|
| HTTP route | httpRouterBuilder:default composition |
| CLI command | cli:command:loader composition |
| Indexer | indexerFacade:items composition |
| ACL resource / role | acl:resource:tuner / acl:default-role:tuner |
| Setup parameter | command:install:definition:tuner + command:install:input:tuner |
| Setup DB step | addSetupDbPackageStep() in diconfig_setup.php |
| DB connection pool | implement ConnectionPool, override the alias in diconfig.php |
| Repository | RepositoryContainer composition keyed by entity type |
| Data-service request handler | dataRouterBuilder:default (addRoute) |
Standards¶
- PHP 8.1+ (8.2+ recommended)
- AMPHP 3.x, Revolt 1.x
- MySQL 8.x (the DB drivers and schema framework target it)
- All I/O is non-blocking; all callbacks must return
void/null
License¶
MIT — Copyright (c) Flyokai (sergey@flyokai.com)
Each vendored fork (flyokai/laminas-db, flyokai/revolt-event-loop, flyokai/amphp-injector, flyokai/zend-db-sql-insertmultiple) carries its upstream license — see the per-module LICENSE file.