tallieutallieu / oak
Simple PHP building block framework
Requires
- php: ^8.4
- dragonmantank/cron-expression: ^3.6
- nyholm/psr7: ^1.8
- nyholm/psr7-server: ^1.1
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- vlucas/phpdotenv: ^5.7
Requires (Dev)
- pestphp/pest: ^5.2
- phpstan/phpstan: ^2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-master
- 4.3.0
- 4.2.2
- 4.2.1
- 4.2.0
- 4.1.0
- 4.0.1
- 4.0.0
- 3.0.14
- 3.0.13
- 3.0.12
- 3.0.11
- 3.0.10
- 3.0.9
- 3.0.8
- 3.0.7
- 3.0.6
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 1.1.15
- 1.1.14
- 1.1.13
- 1.1.12
- 1.1.11
- 1.1.10
- 1.1.9
- 1.1.8
- 1.1.7
- 1.1.6
- 1.1.5
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1
- 1.0.13
- 1.0.12
- 1.0.11
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- dev-feature/sc-11688--oak-allow-class-only-seeder-registration
- dev-chore/sc-11673--dry3-switch-active-packages-to-dry-ci
- dev-chore/sc-11672--dry3-backfill-changelog-md-for-active
- dev-fix/addlistener-generic-over-event
- dev-feature/sc-11568--oak-add-explicit-seeder-execution
- dev-php8.2
- dev-php7
- dev-feature/database
- dev-dev
- dev-feature/filesystem-disks
- dev-feature/scheduler
- dev-feature/http
- dev-feature/migration
- dev-improvement/security
- dev-session-bugfix
This package is auto-updated.
Last update: 2026-09-29 08:29:59 UTC
README
Simple PHP building blocks framework
- Config
- Console
- Container
- Dispatcher
- Filesystem
- Logger
- Scheduler
- Session
Install
composer require tallieutallieu/oak
Creating an application
<?php $app = new \Oak\Application( __DIR__ . '/../', // The path to your .env file __DIR__ . '/../config/', // The path to your config files __DIR__ . '/../cache/', // The path where the application can write cache to ); $app->register([\Oak\Console\ConsoleServiceProvider::class]); $app->bootstrap();
The example above only registers the Console component. This is an easy example since the Console component doesn't depend on any other components. To run the Console component, you'll have to get the Console\Kernel from your application handle the incoming Input:
<?php use Oak\Contracts\Console\InputInterface; use Oak\Contracts\Console\OutputInterface; use Oak\Contracts\Console\KernelInterface; $app->get(KernelInterface::class)->handle( $app->get(InputInterface::class), $app->get(OutputInterface::class), );
To use the HTTP component (PSR-7 & PSR-15 compliant) you'll also have to register the Config component...and since the Config component reads configuration values from the filesystem, you'll also have to register the Filesystem component:
<?php $app->register([ \Oak\Console\ConsoleServiceProvider::class, \Oak\Http\HttpServiceProvider::class, \Oak\Config\ConfigServiceProvider::class, \Oak\Filesystem\FilesystemServiceProvider::class, ]);
Handling an incoming request with the Http\Kernel goes as follows:
<?php use Oak\Contracts\Http\KernelInterface; use Psr\Http\Message\ServerRequestInterface; $app->get(KernelInterface::class)->handle( $app->get(ServerRequestInterface::class), );
Config
<?php $config->set('package', [ 'client_id' => '123', 'client_secret' => 'F1jK4s5mPs9s1_sd1wpalnbs5H1', ]); echo $config->get('package.client_secret'); // F1jK4s5mPs9s1_sd1wpalnbs5H1
Config commands
php oak config clear-cache
php oak config cache
Console
Documentation coming soon
Container
Documentation coming soon
Cookie
Example usage
<?php use Oak\Cookie\Facade\Cookie; Cookie::set('key', 'value'); echo Cookie::get('key'); // value Cookie::delete('key');
Cookie config options
| Name | Default |
|---|---|
| path | / |
| secure | false |
| http_only | true |
| same_site | Lax |
same_site accepts Lax, Strict or None. None is only valid together
with secure; combining it with an insecure cookie throws instead of letting
the browser silently drop the cookie.
securedefaults tofalse. Oak cannot know whether the host serves over TLS, so it does not assume it — which means a project that never writes aconfig/cookie.phpships its session cookie over plain HTTP. Setcookie.securetotruein every project that has TLS.
Dispatcher
<?php use Oak\Dispatcher\Facade\Dispatcher; Dispatcher::addListener('created', function ($event) { echo 'Creation happened!'; }); Dispatcher::dispatch('created', new Event());
Listening for an event class
An event name that is a class name binds its listeners to that class, so a listener can be typed for that class instead of for every event. Dispatch the event object itself: its class is the name, so the listeners are always handed an instance of the class they were registered for.
<?php use Oak\Dispatcher\Event; use Oak\Dispatcher\Facade\Dispatcher; class InvoiceSend extends Event { public function __construct(private Invoice $invoice) {} public function getInvoice(): Invoice { return $this->invoice; } } Dispatcher::addListener(InvoiceSend::class, function (InvoiceSend $event) { $invoice = $event->getInvoice(); }); Dispatcher::dispatch(new InvoiceSend($invoice));
Static analysis knows this too: a listener registered for an event class sees
that class, even when its parameter is left untyped, so calling a method of the
event needs no instanceof guard.
A dispatched event object also reaches the listeners of its parent classes and of the interfaces it implements, after those of its own class. Stopping propagation stops all of them.
An event object is dispatched on its own: passing a second event next to it
throws an InvalidArgumentException. Dispatching by name, as in
Dispatcher::dispatch(InvoiceSend::class, $event), still works but reaches only
the listeners of that exact name. Static analysis requires $event to be an
InvoiceSend there, but cannot tell when it is left out altogether:
Dispatcher::dispatch(InvoiceSend::class) passes analysis and hands the
listeners null. Dispatching the object avoids that.
Any other name is a plain signal. Nothing is known about its event, so its listeners are handed whatever is dispatched, including nothing:
<?php Dispatcher::addListener('app.booted', function () { echo 'Booted!'; }); Dispatcher::dispatch('app.booted');
Isolating listeners
By default a listener that throws takes down every listener registered after it
and the throwable surfaces at the dispatch() call, which quietly makes
registration order load-bearing. A listener can opt out of that:
<?php use Oak\Dispatcher\Facade\Dispatcher; use Oak\Logger\Facade\Logger; // Where throwables from isolated listeners go Dispatcher::setExceptionHandler(function ( Throwable $throwable, string $eventName, callable $listener, ) { Logger::log($eventName . ' listener failed: ' . $throwable->getMessage()); }); // A single listener that must not be able to break the event Dispatcher::addListener('order.paid', $sendConfirmationMail, true); // ...or isolate every listener of one dispatch Dispatcher::dispatchIsolated('order.paid', new Event());
Nothing is swallowed: if no exception handler is configured, the remaining listeners still run and the first throwable is re-thrown once the event is finished.
The $eventName handed to the exception handler is the name the failing
listener was registered under. For a dispatched event object that can be a
parent class or an interface of the event.
Filesystem
Documentation coming soon
Logger
Example usage
<?php use Oak\Logger\Facade\Logger; Logger::log('This message will be logged');
Logger config options
| Name | Default |
|---|---|
| filename | logs/log.txt |
| date_format | d/m/Y H:i |
Session
Example usage
<?php use Oak\Session\Facade\Session; Session::set('key', 'value'); Session::save(); echo Session::get('key'); // value
Rotating and clearing a session
Rotate the session id whenever the privilege level of the session changes — on
login above all — so that an id an attacker planted beforehand is worthless
afterwards. regenerate() mints a new id, carries the data over, drops the old
handler entry and rewrites the cookie:
<?php use Oak\Session\Facade\Session; // After authenticating, before writing the user onto the session Session::regenerate(); Session::set('user_id', $user->id); Session::save(); // On logout Session::destroy();
Session config options
| Name | Default |
|---|---|
| handler | \Oak\Session\FileSessionHandler |
| path | sessions |
| name | app |
| cookie_prefix | session |
| identifier_length | 40 |
| lottery | 200 |
| max_lifetime | 1000 |
