User

The Piko User component is a lightweight user/session manager. It lets you:

Install it as a separate package:

composer require piko/user

This package provides the Piko\User component.

Configuration

Register the User component in your application configuration:

return [
    // ...
    'components' => [
        'Piko\\User' => [
            // FQCN of the identity class implementing Piko\User\IdentityInterface
            'identityClass' => 'app\\modules\\site\\models\\UserIdentity',

            // Optional: session timeout in seconds (see below)
            // 'authTimeout' => 3600,

            // Optional: permission check callback (see "Permissions" section)
            // 'checkAccess' => 'app\\modules\\site\\models\\UserIdentity::checkAccess',
        ],
        // ...
    ],
];

Configuration options:

Once configured, you can retrieve the component from the application:

/** @var Piko\User $user */
$user = $app->getComponent('Piko\\User');

In modular applications, Piko\Module and Piko\Controller can also receive Piko\User via constructor injection (see the authentication example below).

Identity class

To use the user component you must provide an identity class that implements Piko\User\IdentityInterface.

A simplified example based on the default Piko project:

<?php
namespace app\modules\site\models;

use Piko;                       // for Piko::configureObject
use Piko\User\IdentityInterface;

/**
 * Application user identity.
 */
class UserIdentity implements IdentityInterface
{
    public $id;
    public $username;
    public $password;

    /**
     * In a real application, this data would come from a database.
     */
    private static $users = [
        '1' => [
            'id'       => '1',
            'username' => 'admin',
            'password' => 'admin',
        ],
        '2' => [
            'id'       => '2',
            'username' => 'demo',
            'password' => 'demo',
        ],
    ];

    /**
     * Locate a user by username.
     *
     * @param string $username
     * @return static|null
     */
    public static function findByUsername($username)
    {
        foreach (self::$users as $user) {
            if ($user['username'] == $username) {
                $identity = new static();
                Piko::configureObject($identity, $user);

                return $identity;
            }
        }

        return null;
    }

    /**
     * Very basic password check used for the example.
     * In production, use proper password hashing.
     *
     * @param string $password
     * @return bool
     */
    public function validatePassword($password)
    {
        return $this->password == $password;
    }

    /**
     * Find an identity by its primary key.
     *
     * @param string|int $id
     * @return static|null
     */
    public static function findIdentity($id)
    {
        if (isset(self::$users[$id])) {
            $identity = new static();
            Piko::configureObject($identity, self::$users[$id]);

            return $identity;
        }

        return null;
    }

    /**
     * Return the unique identifier for this identity.
     *
     * @return string|int
     */
    public function getId()
    {
        return $this->id;
    }
}

IdentityInterface only requires two static/instance methods:

Additional helper methods such as findByUsername() and validatePassword() are application-specific conveniences used by your controllers.

Authentication

The main authentication entry points are:

A typical controller implementation (again matching the default Piko project):

<?php
namespace app\modules\site\controllers;

use Piko\User;
use app\modules\site\models\UserIdentity;

class DefaultController extends \Piko\Controller
{
    /**
     * The User component is injected automatically based on the
     * component definition in the application config.
     */
    public function __construct(private User $user)
    {
    }

    public function loginAction()
    {
        $error = '';

        if ($this->request->getMethod() === 'POST') {
            $post = $this->request->getParsedBody();

            $identity = UserIdentity::findByUsername($post['username'] ?? '');

            if ($identity instanceof UserIdentity
                && $identity->validatePassword($post['password'] ?? '')
            ) {
                $this->user->login($identity);

                return $this->redirect($this->getUrl('site/default/index'));
            }

            $error = 'Authentication failed';
        }

        return $this->render('login', [
            'error' => $error,
        ]);
    }

    public function logoutAction()
    {
        $this->user->logout();

        return $this->redirect($this->getUrl('site/default/index'));
    }
}

If you do not use constructor injection, you can retrieve the component from the application explicitly:

$app = $this->module->getApplication();
/** @var Piko\User $user */
$user = $app->getComponent('Piko\\User');

$user->login($identity);

Working with the current user

Useful methods exposed by the User component are:

Example:

/** @var Piko\User $user */
$user = $app->getComponent('Piko\\User');

if (!$user->isGuest()) {
    $identity = $user->getIdentity();   // instance of UserIdentity
    $id       = $user->getId();         // string|int

    var_dump($identity);
    var_dump($id);
}

When User::getIdentity() is called for the first time in a request, the component:

  1. Ensures the session is started.
  2. Reads $_SESSION['_id'] (if present).
  3. Calls YourIdentityClass::findIdentity($id) to reconstruct the identity.

The identity is then cached in memory for the duration of the request.

Permissions

User::can() is used to check whether the current user has a given permission:

if ($user->can('editPost')) {
    // allow editing the post
}

User::can() delegates the decision to the checkAccess callback configured on the User component. The callback receives two arguments:

function checkAccess($userId, string $permission): bool

The return value must be a boolean. User::can() caches the result per permission name for the lifetime of the User instance.

Example configuration using a static method on the identity class:

return [
    // ...
    'components' => [
        'Piko\\User' => [
            'identityClass' => 'app\\modules\\site\\models\\UserIdentity',
            'checkAccess'   => 'app\\modules\\site\\models\\UserIdentity::checkAccess',
        ],
        // ...
    ],
];

And a minimal implementation in UserIdentity:

class UserIdentity implements IdentityInterface
{
    // ... existing code ...

    public static function checkAccess($userId, string $permission): bool
    {
        // Example: simple hard-coded permissions
        if ($userId === null) {
            return false; // guests have no permissions
        }

        if ($permission === 'adminOnly') {
            return $userId === '1'; // only user with ID 1
        }

        return true; // allow everything else
    }
}

You are free to implement any permission strategy you like (role-based access control, per-resource ACL, etc.) as long as the callback matches the expected signature and returns a boolean.

Best practices

The examples above are intentionally simple to focus on how the User component works. In real-world applications, consider the following recommendations: