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:

Use Piko::getAlias() to resolve an alias into its concrete value, and Piko::setAlias() to register or override an alias.

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:

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:

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:

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();