Concepts
Aliases
Aliases are a convenient way to work with filesystem paths and base URLs inside a Piko
application. An alias is a short name prefixed with @ that represents a longer
path (directory, file path, URL, etc.).
During application initialisation, Piko\Application registers three aliases:
@app– the application root path (basePathin the configuration)@webroot– the public web directory (defaults tobasePath . '/web'unlesswebrootis provided in the configuration)@web– the base URI of the application (from the optionalbaseUrlsetting)
Use Piko::getAlias() to resolve an alias
into its concrete value, and Piko::setAlias()
to register or override an alias.
- If the string passed to
getAlias()does not start with@, it is returned unchanged. - If the alias prefix is unknown,
getAlias()returnsfalse.
Example:
// Built-in aliases
echo Piko::getAlias('@app/modules/site');
// /usr/local/share/myapp/modules/site
echo Piko::getAlias('@webroot/documents');
// /var/www/documents
echo Piko::getAlias('@web/css/styles.css');
// /css/styles.css
// Custom alias
Piko::setAlias('@lib', '/usr/local/share/lib');
echo Piko::getAlias('@lib/pdf/manual.pdf');
// /usr/local/share/lib/pdf/manual.pdf
Components
Some objects must be shared across the whole application (database connections, loggers, view renderers, user session managers, and so on). In Piko, these application-wide singletons are called components.
Components are declared in the components section of the application
configuration. The configuration is a
key–value array where keys are usually the component class names and values
can be one of the following:
- Array of public properties with an optional
constructkey specifying constructor arguments (lazy-loaded). The array key should be the fully qualified class name. - Array with a
classkey plus optionalconstructand other public properties. This allows you to use a custom identifier as the array key. - Already instantiated object (eagerly available).
- Callable that returns an instance (lazy-loaded factory).
Example component configuration:
use Piko\View;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
return [
// ...
'components' => [
// 1. View component configured via public properties
View::class => [
// Default charset is UTF-8
'charset' => 'ISO-8859-1',
],
// 2. Logger component created lazily via a factory
Logger::class => function (): Logger {
$logger = new Logger('app');
$logger->pushHandler(
new StreamHandler(__DIR__ . '/../var/log/app.log', Logger::DEBUG)
);
return $logger;
},
// 3. PDO configured using constructor arguments (lazy-loaded)
PDO::class => [
'construct' => [
'mysql:dbname=' . getenv('MYSQL_DB') . ';host=' . getenv('MYSQL_HOST'),
getenv('MYSQL_USER'),
getenv('MYSQL_PASSWORD'),
],
],
// 4. Alternative style: custom id with an explicit class
'mailer' => [
'class' => Nette\Mail\SmtpMailer::class,
'construct' => [
getenv('SMTP_HOST'),
getenv('SMTP_USER'),
getenv('SMTP_PASSWORD'),
(int) getenv('SMTP_PORT'),
getenv('SMTP_ENCRYPTION'),
],
],
],
];
Once declared, component instances are retrieved via
Application::getComponent():
/** @var PDO $db */
$db = $app->getComponent(PDO::class);
Array-based definitions are lazy-loaded: they are converted internally into
factories and instantiated the first time you call getComponent(). Objects and
callables that you provide directly are used as-is.
Application::getComponent() is also used by Piko\Module when creating
controllers and other services: any constructor parameter that has a
non-builtin class type-hint will be resolved from the components container
(using the class name as the key). This is how dependency injection is
implemented in Piko.
Events
Piko supports dispatching and listening for events via the
Piko\EventHandlerTrait. Any class using this
trait becomes an event handler: it can register listeners and dispatch
events.
class MyConnectEvent
{
public string $username = '';
public function __construct(string $username)
{
$this->username = $username;
}
}
class MyClass
{
use Piko\EventHandlerTrait;
private string $username = '';
public function connect(): void
{
// Dispatch the event and retrieve the (possibly modified) instance
$event = $this->trigger(new MyConnectEvent($this->username));
echo $event->username;
}
}
Two methods are provided by the trait:
-
$handler->on(string $eventClassName, callable $listener, ?int $priority = null);Registers a listener for the given event class.
priorityis optional; higher values are executed earlier. -
$event = $handler->trigger(object $event);Dispatches the event and returns the same instance after all listeners have run. Listeners can modify the event object in-place.
Example of registering and handling an event:
$c = new MyClass();
$c->on(MyConnectEvent::class, function (MyConnectEvent $event): void {
$event->username = 'Paul';
});
$c->connect(); // outputs "Paul"
Internally, EventHandlerTrait relies on the
piko/event-dispatcher
package, which is a PSR-14 compliant
implementation.
Behaviors
The Piko\BehaviorTrait can be used to inject
custom methods into an object instance at runtime. Methods added this way are
called behaviors.
To attach a behavior, use
attachBehavior().
Example:
class MyClass
{
use Piko\BehaviorTrait;
}
$c = new MyClass();
// Attach a new "disconnect" behavior
$c->attachBehavior('disconnect', function (): void {
echo 'I am disconnected!';
});
$c->disconnect(); // I am disconnected!
BehaviorTrait stores behaviors in the public $behaviors array and uses
__call() to intercept calls to undefined methods. If a method name matches a
registered behavior, the associated callback is executed. See the
API reference for details on
detachBehavior() and supported callback forms.
Middleware
To produce a response, a Piko application sends an HTTP request through a FIFO queue of middlewares.
A middleware is a component that sits between the web server and your application. It can inspect and/or modify the incoming request and the outgoing response, and either short-circuit the pipeline or delegate to the next middleware.
Piko middlewares implement the PSR-15
Psr\Http\Server\MiddlewareInterface,
while the application (Piko\Application and Piko\ModularApplication)
implements
Psr\Http\Server\RequestHandlerInterface.
Conceptually, the pipeline looks like this:
App --> request --> MiddlewareA --> request --> MiddlewareB
App <-- response <-- MiddlewareA <-- response <-- MiddlewareB
The request is an instance of
Psr\Http\Message\ServerRequestInterface
and the response an instance of
Psr\Http\Message\ResponseInterface.
ModularApplication pipeline
Piko\Application maintains an internal FIFO queue of middlewares. You can add
a middleware with Application::pipe() before calling run().
Piko\ModularApplication extends Application and defines the default
pipeline used by modular apps:
- On construction it installs an
ErrorHandleras$app->errorHandler. - When you call
ModularApplication::run(), it automatically enqueues a singleRoutingMiddleware, which resolves the route, boots configured modules and dispatches the request to the appropriate controller.
Any custom middlewares you pipe() are executed in the order they were
added, before RoutingMiddleware.
Example: CORS middleware
The following middleware injects an Access-Control-Allow-Origin header into
all responses:
namespace app\lib;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class CorsMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$response = $handler->handle($request);
return $response->withHeader('Access-Control-Allow-Origin', '*');
}
}
In the entry script:
use Piko\ModularApplication;
use app\lib\CorsMiddleware;
require __DIR__ . '/../vendor/autoload.php';
$config = require __DIR__ . '/../config.php';
$app = new ModularApplication($config);
$app->pipe(new CorsMiddleware()); // executes before RoutingMiddleware
$app->run();