ionzile / core
Ions core library
Requires
- php: ^8.3
- ext-openssl: *
- ext-sodium: *
- ext-zip: *
- cartalyst/sentinel: ^9.0
- dragonmantank/cron-expression: ^3
- illuminate/cache: ^12.0
- illuminate/console: ^12.0
- illuminate/container: ^12.0
- illuminate/database: ^12.0
- illuminate/events: ^12.0
- illuminate/filesystem: ^12.0
- illuminate/http: ^12.0
- illuminate/pagination: ^12.0
- illuminate/queue: ^12
- illuminate/support: ^12.0
- illuminate/validation: ^12.0
- intervention/image: ^3
- lcobucci/clock: ^3.3
- lcobucci/jwt: ^5.0
- league/flysystem: ^3.29.1
- league/flysystem-aws-s3-v3: ^3.29
- league/flysystem-ftp: ^3
- league/flysystem-memory: ^3
- league/flysystem-sftp-v3: ^3
- monolog/monolog: ^3.0
- nyholm/psr7: ^1.8
- peppeocchi/php-cron-scheduler: ^4.0
- psr/container: ^2.0
- psr/http-server-middleware: ^1.0
- symfony/config: ^7.0
- symfony/http-client: ^7.0
- symfony/mailer: ^7.0
- symfony/psr-http-message-bridge: ^7.4
- symfony/routing: ^7.0
- symfony/security-csrf: ^7.0
- symfony/translation: ^7.0
- symfony/yaml: ^7.0
- twig/twig: ^3.10
- vlucas/phpdotenv: ^5.6.1
Requires (Dev)
- driftingly/rector-laravel: ^2.5
- fakerphp/faker: ^1.24
- friendsofphp/php-cs-fixer: ^3.64
- jetbrains/phpstorm-attributes: ^1.0
- pestphp/pest: ^3.0
- phpstan/phpstan: ^2.0
- rector/rector: ^2.0
This package is auto-updated.
Last update: 2026-06-13 09:41:55 UTC
README
A lightweight PHP 8.3+ framework built on Symfony HTTP and routing components and Illuminate 12 (database, cache, queue, events, console, container) — providing a structured, secure foundation without the full overhead of a monolithic framework.
Requirements
- PHP 8.3 or 8.4
- Extensions:
openssl,zip - Composer
Installation
composer require ionzile/core
Quick Start
Front controller (public/index.php)
<?php require __DIR__ . '/../vendor/autoload.php'; use Ions\Foundation\Kernel; Kernel::boot(); Kernel::run();
Kernel::boot() loads .env, boots the container, registers service providers, and loads routes. Kernel::run()
handles the request and sends the response. Existing front controllers that call Kernel::make() continue to work — it
is a thin BC shim around run().
Serve it locally with php bin/ions serve (PHP's built-in dev server on
http://127.0.0.1:8000; --host/--port to change — php -S localhost:8000 -t public
remains the raw alternative).
Route (routes/web.php)
<?php use App\Http\Controllers\UsersController; use Ions\Bundles\Route; Route::get('/users', UsersController::class . '::index'); Route::prefix('/api/v1')->group(function () { Route::post('/users', 'UserController@store')->middleware(['throttle']); });
Controller (app/Http/Controllers/UsersController.php)
<?php use App\Services\UserService; use Ions\Foundation\BaseController; use Ions\Support\Request; use Ions\View\View; class UsersController extends BaseController { public function __construct(private readonly UserService $users) // container-built (4.2) { parent::__construct(); } public function index(Request $request): View { // Controller-relative view: renders views/users/index.twig as a 200 response return $this->view('index', ['users' => $this->users->all()]); } }
Actions are method-injected (the request, route placeholders by name, services by type-hint) and may return a
View (view() / $this->view()), a Symfony\Component\HttpFoundation\Response, or any Ions\Http\Responsable
(e.g. an API Resource) — the framework normalises the return value before sending. An Eloquent-typed parameter named
after a placeholder is route-model-bound (show(Widget $widget) on /widgets/{widget} — fetched or 404, 4.3). API
controllers extend Ions\Foundation\ApiController and return JSON (Json::ok([...])). Authorization is one call:
$this->authorize('update', $post) in an action, can('update', $post) anywhere, {% if can('update', post) %} in
Twig (docs/auth.md). See docs/controllers.md.
Scheduled tasks (app/Schedule.php)
<?php namespace App; use Ions\Schedule\Scheduler; class Schedule { public static function boot(Scheduler $schedule): void { $schedule->command('emails:send')->daily()->withoutOverlapping(); } }
One crontab line runs everything that is due: * * * * * cd /path/to/app && php bin/ions schedule:run — see
docs/scheduler.md.
Starting a new app? Copy the host-app skeleton (skeleton/) and read
docs/best-practices.md — the opinionated guide to structuring an Ions application.
Application Layout
your-app/
├── public/
│ └── index.php # Front controller
├── config/
│ ├── app.php # Framework config (providers, middleware, jwt, …)
│ ├── auth.php # Auth provider / table / columns
│ └── database.php # DB connections
├── routes/
│ ├── web.php # Web routes
│ └── api.php # API routes
├── app/ # Application source (or legacy src/)
│ ├── Http/ # Controllers (web)
│ └── Models/ # Eloquent models (App\Models, make:model)
├── database/ # Host-root layout (4.4): migrations/, seeders/,
│ │ # factories/, schemas/, backups/
│ ├── migrations/
│ ├── seeders/ # Database\Seeders
│ └── factories/ # Database\Factories
├── views/
│ └── default/ # Twig templates
├── var/
│ └── cache/ # Twig, rate-limit, revocation caches
└── .env
The framework resolves application paths relative to the host-app root (five directory levels above vendor/). Use
Ions\Bundles\Path helpers (Path::src(), Path::config(), Path::views(), etc.) to reference locations portably.
Both directory names are supported: app/ is checked first (the convention since 4.2), with src/ as the preserved legacy fallback.
Features
- PSR-11 container (
Ions\Container\Container) withbind,singleton,make - Service providers (
Ions\Container\ServiceProvider) with two-pass bootstrap - Provider auto-discovery — zero-config registration from
{app|src}/Providers/and composer packages declaringextra.ions.providers; escape hatchesapp.providers/app.discovery/app.dont_discover - Production caches —
ions optimize(route:cache+config:cache+discover:cache),optimize:clear,preload:generate; all bypassed whileAPP_DEBUGis on - Worker mode (stable) —
Kernel::resetForRequest()+Ions\Runtime\WorkerRunnerfor boot-once/handle-many runtimes (FrankenPHP/RoadRunner recipes) - Middleware pipeline —
MiddlewareInterface,Pipeline, per-route->middleware([...]) - Default stacks: web (TrustedHost + SecurityHeaders + CORS + CSRF), api (+ AuthMiddleware)
- Routing —
Route::get/post/put/patch/delete/any/match/resource, fluent->name()/->where()/->middleware(),Route::redirect/view/fallbackshortcut routes, prefix/group nesting with name + middleware prefixes, attribute routing (#[Route]),route()URL helper - Route model binding — Eloquent-typed, placeholder-named action params resolve to the fetched record
(
getRouteKeyName()) or 404; nullable params get null on a miss - Trusted proxies —
app.trusted_proxies(IPs/CIDRs or'*') with fail-closed header sets (xff/aws-elb/traefik/forwarded); proxy-awareisSecure(), HSTS andcookie_secure => 'auto'behind TLS-terminating LBs - JWT auth (
Ions\Security\Jwt) — access + refresh tokens, revocation deny-list, clock leeway - HTTP auth endpoints —
Ions\Auth\Http\AuthController(login / refresh / logout / password reset); per-user-bound tokens; refresh-token rotation with family reuse detection (replay revokes the whole family) (4.4) - TOTP two-factor (
Ions\Auth\TwoFactor) — RFC 6238 verifier with drift window, recovery codes, otpauth URI for QR provisioning, single-use replay store (4.5) - Email verification (
Ions\Auth\EmailVerification) — signed links bound to the current email,VerifiesEmailcontract,verifiedmiddleware,VerifyEmailnotification, resend throttle (4.5) - Pluggable auth —
UserProvidercontract;SentinelUserProvider(default) orEloquentUserProvider - Authorization —
Ions\Auth\Gateabilities + model policies:allows/denies/authorize(403 on deny),can()helper + Twigcan()function,$this->authorize()in controllers, guest auto-deny - Multi-driver filesystem —
Ions\Filesystem\Storage/FilesystemManager;local,s3,ftp,sftp,memory+ custom drivers;putFile/download()/url()/temporaryUrl()/listings;Storage::fake()also intercepts the legacyIonDisk/IonUpload(unified in 4.3) - Session —
Ions\Session\SessionManager;session()helper; CSRF stored in the session - Console —
bin/ionsrunner +Ions\Console\Kernel; command discovery;make:command,queue:work,serve(PHP dev server),down/up(maintenance mode) - Cron scheduler —
App\Schedule::boot(Scheduler)fluent tasks (->daily(),->withoutOverlapping(), …);schedule:run/schedule:list;/cron/scheduleweb-cron parity - Host-app diagnostics —
ions doctor(env, APP_KEY, writablevar/, caches, DB, extensions, security posture);--jsonfor CI; exits non-zero on critical misconfig - Cache / Queue / Events —
cache()/dispatch()/event()+listen()helpers; Illuminate-backed providers - Queue resilience —
$tries/$backoffjob properties, failed-jobs store (queue.failed),queue:failed/queue:retry/queue:forget/queue:flush - Pagination + web form flow —
$query->paginate(n)+ Twigpagination(); session flash,old()/errors(), fluentredirect()->back()->withErrors()->withInput()(same-originback()guard) - Channel logging —
Ions\Support\Logfacade overconfig/logging.php(single/daily/stderr/stackdrivers, per-channel levels), secret redaction + per-request id correlation - Outbound HTTP client —
Ions\Support\Httpfacade oversymfony/http-client(retry/timeout/token builder);Http::fake()for tests - Mailables —
Ions\Mail\Mailable(build/send/queue with Twig views);Mail::fake()FQCN assertions - Notifications —
Ions\Notifications\Notificationwith mail + database channels, custom channels,notify();Notifications::fake() - Host-root
database/layout — Laravel-standarddatabase/tree (migrations/seeders/factories/schemas/backups) with precedence over legacy{app|src}/Database;Database\Factories/Database\Seedersnamespaces;make:model→app/Models(App\Models,--factoryflag);ions doctordual-directory warning (4.4) - Model factories —
Ions\Database\Factory(make/create/count/state),HasIonsFactory(explicit$factory→Database\Factories→{ModelNs}\Factoriesresolution), Faker integration - Testing kit —
Ions\Testing\TestCase+TestResponse(verb helpers,actingAs()real JWT) plus Queue/Event/Storage/Mail/Notifications/Http fakes - N+1 query detector — debug-only repeated-pattern warnings to
var/logs/performance.log - ORM strict mode (debug) — lazy-load and silently-discarded-fill violations throw under
APP_DEBUG;database.strict => falseescape hatch - Health endpoint — built-in
/upliveness probe + token-gated?checks=1doctor JSON;Cache-Control: no-store - Debugging UX — interactive debug page (clickable frames, server-side syntax highlighting, redacted
request/headers/params/cookies/session/context tabs, copy-as-markdown, open-in-editor) + expandable debug toolbar
(query/request/timing panels, query cap) + branded production error pages (400–503, host-template override); shared
Ions\Http\Uidesign system, dependency-free + self-contained + dev-only gated (14.6) - Maintenance mode —
ions down [--secret] [--retry]/ions up; themeable 503 (errors/503.twig), HMAC-bound bypass cookie,/upstays reachable - PSR-15 adapter —
Psr15Adapterruns any PSR-15 middleware in the Ions pipeline (nyholm/psr7 + Symfony bridge) - API resources —
Ions\Http\Resource/ResourceCollection(singledataenvelope, pagination meta/links);FormRequest;openapi:generate - Image processing —
Ions\Media\Imageoverintervention/imagev3 (resize / crop / cover / watermark / encode) - Typed config accessors —
config()->string('app.name')/integer()(int()) /boolean()(bool()) /array()/float(); throwInvalidArgumentExceptionon type mismatch (no coercion) - JSON helpers —
Json::ok()/Json::error() - Twig views —
Ions\View\ViewFactory; bound asviewin the container - Security headers on every response; configurable CSP
- CSRF enforcement on web routes (opt-out via
app.csrf.enabled = false) - Upload validation — extension allow-list + hard-coded executable deny-list
- Encryption & signed URLs —
Ions\Security\Encrypter(XChaCha20-Poly1305 AEAD),UrlSigner+signedRoute()helper +signedmiddleware alias - Rate limiting —
RateLimitMiddleware/throttlealias, 429 +Retry-After - Response cache — opt-in full-page cache for anonymous GET 200s (
cache.responsealias); ETag/304,Vary, TTL,X-Ions-CacheHIT/MISS, never caches auth/session, debug bypass;ions cache:clear-responses(≈10–12× faster cached vs live render inbench/bench.php) - Exception handler —
Ions\Http\ExceptionHandler; JSON for API (incl. 422 validation), HTML for web with host-themeableviews/errors/{status}.twigpages (4.3); web validation failures redirect back with errors + input; safe in production - Generators —
make:model(app/Models,--factory),make:middleware,make:service-provider,make:command,make:resource,make:request,make:job,make:event,make:listener,make:test,make:factory,make:seeder - Host-app skeleton —
skeleton/: a minimal bootable host layout with the 4.1 secure defaults pre-filled - Debug error page — interactive: clickable frames, highlighted source, redacted request tabs, copy/open-in-editor
(
APP_DEBUG=trueonly) — see Debugging UX above - IDE support —
.phpstorm.meta.phpships with the package, so PhpStorm infers concrete types forapp('id')and containerget()/make()lookups automatically - CI — PHPStan (level 5 full / level 8 core), PHP-CS-Fixer, Rector (Laravel 12), Pest 3 (PHP 8.3 + 8.4 × SQLite + MySQL 8)
Documentation
| Document | Contents |
|---|---|
| docs/best-practices.md | The opinionated guide: app/ structure, thin controllers, providers/DI, events vs jobs vs notifications, testing, security & performance checklists |
| docs/skeleton.md | Host-app skeleton (skeleton/): layout, quick-start, secure defaults |
| docs/testing.md | Host-app test kit: Ions\Testing\TestCase, verb helpers, actingAs() (real JWT), TestResponse assertions |
| docs/factories.md | Minimal model factories: Ions\Database\Factory, make()/create()/count()/state(), HasIonsFactory, make:factory |
| docs/lifecycle.md | Boot sequence, request pipeline, response dispatch |
| docs/routing.md | Route registration, prefixes, groups, middleware, attributes |
| docs/controllers.md | Controller lifecycle (legacy + boot/beforeAction/afterAction), constructor/action DI, middleware(), return normalization |
| docs/middleware.md | MiddlewareInterface, pipeline, default stacks, writing middleware |
| docs/views.md | Twig views: view() renderable returns, namespaced roots (app.twig.paths), controller-relative $this->view(), pagination() |
| docs/forms.md | Web form flow: flash data, old()/errors(), fluent redirect()/back(), FormRequest web redirects, app.forms.dont_flash |
| docs/assets.md | Frontend assets: install:vue (Vue 3 + Vite, hot-file dev mode), install:assets (no-build starters), vite()/asset() Twig functions |
| docs/auth.md | UserProvider, JWT, AuthController endpoints, AuthMiddleware, rate limiting, CSRF |
| docs/two-factor.md | TOTP two-factor (Ions\Auth\TwoFactor): RFC 6238 verifier, drift window, otpauth URI → QR, recovery codes, replay store, encrypt-at-rest, login challenge |
| docs/email-verification.md | Email verification (Ions\Auth\EmailVerification): signed links bound to the current email, VerifiesEmail contract, verified middleware, VerifyEmail notification, resend throttle |
| docs/filesystem.md | Multi-driver disks, Storage, FilesystemManager, uploads |
| docs/session.md | SessionManager, session(), StartSessionMiddleware, CSRF |
| docs/cache-queue-events.md | cache() / dispatch() / event()+listen(), jobs, retries/backoff, failed jobs, queue:work / queue:retry |
| docs/logging.md | Channel logging: Log facade, config/logging.php drivers (single/daily/stderr/stack), request-id correlation, redaction, Logs::create() legacy |
| docs/mail.md | Mailable classes (build/send/queue, Twig views), Mail facade, Mail::fake() FQCN assertions, newMailerDsn() |
| docs/notifications.md | Notification classes (via()/toMail()/toDatabase()), mail recipient routing, notifications table stub, custom channels, notify(), Notifications::fake() |
| docs/console.md | Console Kernel, bin/ions, command discovery, make:command, doctor diagnostics |
| docs/scheduler.md | Cron scheduler: App\Schedule::boot(Scheduler), frequencies, withoutOverlapping(), schedule:run/schedule:list, web-cron |
| docs/resources.md | API resources, collections, form requests, openapi:generate |
| docs/media.md | Image processing over intervention/image v3 |
| docs/http-client.md | Outbound HTTP: Http facade over symfony/http-client, response wrapper, Http::fake() |
| docs/security.md | Encrypter (sodium AEAD), UrlSigner, signedRoute()/signedUrl(), signed middleware |
| docs/security-audit-bundles.md | Legacy Bundles/ security audit: upload/disk path-traversal containment in Path, SVG/HTML/JS/XML deny-list, fail-closed upload content validation, IonDisk::getSignedUrl() presigning |
| docs/response-cache.md | Opt-in HTTP response caching: ResponseCache, cache.response middleware, ETag/304 revalidation, auth/session-safe rules, cache:clear-responses |
| docs/errors-and-debugging.md | Debugging UX: interactive debug page (frames/tabs/copy/open-in-editor, redaction), expandable debug toolbar (query/request/timing panels, query cap), branded production error pages + host overrides, app.debug.editor / app.debug_toolbar |
| docs/config.md | All app.*, auth.*, filesystem.*, session.*, cache.*, logging.*, queue.*, events.*, media.*, notifications.* config keys |
| docs/packages.md | Building Ions packages: extra.ions.providers zero-config discovery, provider conventions, package commands |
| docs/performance.md | Production caches (optimize, route/config/discover), opcache preload, N+1 detector, measured numbers |
| docs/worker-mode.md | Worker mode (stable): Kernel::resetForRequest(), WorkerRunner, per-request state isolation matrix, FrankenPHP + RoadRunner recipes |
| docs/deploy.md | Deployment: nginx/Apache configs, public/.htaccess, PHP-FPM pool notes, TLS-proxy caveat, deploy checklist |
| CHANGELOG.md | What changed in each release |
| UPGRADE-4.6.md | Breaking change: migration/dump folders swapped to the Laravel layout (4.5 → 4.6) |
| UPGRADE-4.5.md | Behavior changes and migration guide for 4.4 → 4.5.0 |
| UPGRADE-4.4.md | Behavior changes and migration guide for 4.3 → 4.4.0 |
| UPGRADE-4.3.md | Behavior changes and migration guide for 4.2 → 4.3.0 |
| UPGRADE-4.2.md | Behavior changes and migration guide for 4.1 → 4.2.0 |
| UPGRADE-4.1.md | Behavior changes and migration guide for 4.0 → 4.1.0 |
| UPGRADE-4.0.md | Breaking changes and migration guide for 3.x → 4.0.0 |
| UPGRADE-3.0.md | Breaking changes and migration guide for 2.1.x → 3.0.0 |
Reference application
examples/taskflow/ is a complete, working host
application — a project/task tracker — that exercises every framework
subsystem once and ships its own Pest suite as living documentation. It is
path-linked to this working-tree core (repositories: path → ../../,
symlinked), so it runs against the exact code in this repo and a dedicated
CI example job keeps it green.
It dogfoods the public API end-to-end: registration + signed email
verification, TOTP two-factor, session + JWT auth with Gate policies,
project/task CRUD with route-model binding, FormRequests, pagination and
IonUpload attachments, queued jobs, notifications (mail + database),
mailables, the cron scheduler, signed/expiring share links, response
caching, encryption-at-rest, and the outbound HTTP client. See its
README for the full feature → subsystem map
and tests/FeatureJourneyTest.php for the whole journey in one narrative.
License
MIT — see LICENSE.md.