hydrakit / app
Hydra PHP framework application skeleton
Requires
- php: >=8.2
- hydrakit/auth: ^0.1
- hydrakit/authorization: ^0.1
- hydrakit/console: ^0.1
- hydrakit/core: ^0.1
- hydrakit/csrf: ^0.1
- hydrakit/database: ^0.1
- hydrakit/event: ^0.1
- hydrakit/http: ^0.1
- hydrakit/kernel: ^0.1
- hydrakit/log: ^0.1
- hydrakit/nyholm: ^0.1
- hydrakit/session: ^0.1
- hydrakit/validation: ^0.1
- hydrakit/view: ^0.1
- nyholm/psr7: ^1.8
- php-di/php-di: ^7.0
- symfony/console: ^7.0
Requires (Dev)
- phpunit/phpunit: ^11.0
README
The starting point every Hydra project is created from. It's the composition root that wires the framework packages together and the home for everything that is application policy rather than framework mechanism: the user provider, the database connection, the view layer, the abilities.
Requirements
- PHP 8.2+ and Composer (for the no-Docker path).
- Docker + Compose (for the full stack: PHP-FPM, nginx, MariaDB, Redis).
Quick start
cp .env.example .env # the defaults run as-is for local dev composer install # resolves the symlinked framework packages php bin/console key:generate # writes a fresh APP_KEY into .env
Then bring it up one of two ways.
With Docker (full stack — recommended):
./bin/dev up -d --build # PHP-FPM + nginx + MariaDB + Redis
Open http://localhost:8080 (the port is APP_PORT in .env). You should see
Welcome to Hydra.
Without Docker (the public site only — no DB needed for hello world):
composer start # php -S localhost:8000 -t public/
Open http://localhost:8000.
Updating
composer install # or: ./bin/exec composer install php bin/console migrate:run # apply any new migrations (DB only)
Everyday commands
./bin/dev up -d # start the dev stack ./bin/dev down # stop it ./bin/dev logs -f nginx # tail a service ./bin/exec # shell in the php container ./bin/phpunit # run the test suite ./bin/db # open a MariaDB shell php bin/console # list everything the console can do php bin/console make:user # create a login (prompts for the password) php bin/console make:controller Posts # scaffold App\Controllers\PostsController
make:* generators match the existing conventions and refuse to overwrite
without --force. make:controller prints a reminder to register the class in
AppServiceProvider::CONTROLLERS — that list is the one place the whole route
contract is read, so it's edited by hand on purpose.
How it's put together
A request enters at public/index.php, which calls App\Bootstrap::application()
to build the container, register AppServiceProvider, and run the HTTP kernel.
bin/console builds the same composition root, so commands resolve the exact
bindings the web app runs with.
What the app fulfils. The framework packages leave the contracts they can't
know to the app, and AppServiceProvider binds them at the composition root —
the user provider (UserRepository), the PSR-7 request provider, the abilities
(e.g. AccessAdmin), and the concrete container, database connection, view
layer, and logger. All of these are application choices.
Data. Repositories are hand-written over a thin ConnectionInterface (a PDO
seam) and return typed entities — no ORM. The app talks to MariaDB; the test
suite runs the same repositories against SQLite through the same seam.
Migrations are plain, forward-only .sql files in database/migrations,
applied in lexical order (the {Ymd_His}_name.sql prefix is the ordering key).
A migrations table records what has run, so re-applying is a no-op.
php bin/console make:migration "create posts table" # scaffold an empty timestamped .sql php bin/console migrate:run # apply all pending php bin/console migrate:status # applied vs pending php bin/console migrate:fresh # drop all + re-run (dev only; needs --force when debug is off)
Views. PhpView is native-PHP templating behind a ViewInterface seam:
escape-by-default, with Twig-style extends/sections via Template. The
Htmx helpers negotiate full-page vs. fragment responses.
Middleware. The global stack is declared in AppServiceProvider::MIDDLEWARE
(outermost first) and resolved through the container, so each layer gets full
DI. The defaults apply application policy the framework leaves open: request
logging, security headers (the single server-agnostic source of truth — the
nginx conf deliberately does not set them too), optional HTTPS forcing
(FORCE_HTTPS), and the unauthenticated → /login redirect.
Configuration
Everything is environment-driven via .env (see .env.example for the full,
commented list). The defaults are tuned for local dev; the ones that matter most:
| Variable | What it does |
|---|---|
APP_KEY |
Signing key. Generate with key:generate before first boot. |
APP_DEBUG |
true locally; set false in production. |
APP_PORT |
Host port the dev stack publishes nginx on (8080). |
ROUTE_CACHE |
Compile routes to a cache. Off in dev; on in prod via route:cache. |
FORCE_HTTPS |
Redirect to https + HSTS. Off by default so local http works. |
DB_* / REDIS_* |
MariaDB and Redis connection settings. |
Tests
./bin/phpunit # whole suite, in the container ./bin/phpunit --filter SomeTest # a subset ./bin/phpunit --testdox # readable output