Creating a component

Step-by-step example based on AuthAdmin from skeleton: admin login, dashboard, user CRUD, migrations, seeder, and CLI command.

Prerequisites: component concept, global middleware in routes/web.php.

1. Directory and component class

The component class implements ComponentInterface directly — metadata, routes, migrations, views, and assets are declared in methods (not a separate config.php). Inject PathManager to build paths relative to the application root.

// src/Components/AuthAdmin/AuthAdminComponent.php
namespace Concept\Components\AuthAdmin;

use Concept\Components\AuthAdmin\Commands\UserListCommand;
use Concept\Components\AuthAdmin\Database\Seeders\UserSeeder;
use Concept\Components\AuthAdmin\Extensions\TwigExtension;
use Concept\Core\Foundation\PathManager;
use Concept\Core\Foundation\PathName;
use Concept\Core\Services\Component\Contracts\ComponentInterface;

class AuthAdminComponent implements ComponentInterface
{
    private const string NAME = 'AuthAdmin';
    private const string VERSION = '1.0.0';
    private const string DESCRIPTION = 'Admin authentication, dashboard and user management.';
    private const string VIEW_NAMESPACE = 'auth-admin';

    protected string $componentDir = __DIR__;

    public function __construct(private readonly PathManager $pathManager) {}

    public function name(): string { return self::NAME; }
    public function version(): string { return self::VERSION; }
    public function description(): string { return self::DESCRIPTION; }

    public function routes(): ?string
    {
        return $this->componentDir . '/routes.php';
    }

    public function providers(): array { return []; }

    public function seeders(): array { return [UserSeeder::class]; }

    public function migrationPaths(): array
    {
        return [
            $this->pathManager->toRelative($this->componentDir . '/Database/Migrations'),
        ];
    }

    public function commands(): array { return [UserListCommand::class]; }

    public function viewExtensions(): array { return [TwigExtension::class]; }

    public function viewPaths(): array
    {
        return [
            self::VIEW_NAMESPACE => $this->pathManager->toRelative($this->componentDir . '/Views'),
        ];
    }

    public function viewContexts(): array
    {
        return ['/admin' => 'dashboard'];
    }

    public function assets(): array
    {
        return [
            $this->pathManager->toRelative($this->componentDir . '/Assets/admin-tokens.js') =>
                $this->pathManager->getRelative(PathName::PUBLIC, 'components/auth-admin/js/admin-tokens.js'),
        ];
    }
}
  • viewPaths() — template @auth-admin/loginViews/login.twig
  • viewContexts() — URLs under /admin get route_namespace = dashboard (see Views)
  • providers() — optional DI service providers (see AclComponent, DebugBarComponent)

2. Application registration

// config/components.php
use Concept\Components\AuthAdmin\AuthAdminComponent;

return [
    'components' => [
        AuthAdminComponent::class => AuthAdminComponent::class,
    ],
];

3. Routes

// src/Components/AuthAdmin/routes.php
/** @var Router $router */

$router->get('/admin/login', [AdminController::class, 'showLogin'])->setName('admin.login');
$router->post('/admin/login', [AdminController::class, 'login'])->setName('admin.login.submit');

$router->group('/admin', function (RouteGroup $route) {
    $route->get('/dashboard', [AdminController::class, 'index'])->setName('admin.dashboard');
    $route->get('/logout', [AdminController::class, 'logout'])->setName('admin.logout');
})->lazyMiddleware(AuthMiddleware::class);

$router->group('/admin/users', function (RouteGroup $route) {
    $route->get('/', [UserController::class, 'index'])->setName('admin.users');
    $route->post('/store', [UserController::class, 'store'])->setName('admin.user.store');
    // ...
})->lazyMiddleware(AuthMiddleware::class);

Public login — without AuthMiddleware; protected groups — with module middleware. Route names (admin.*) are used in redirectByName() and Twig uri().

4. Authorization middleware

// Middlewares/AuthMiddleware.php — session check, admin role, ACTIVE status
public function process(...): ResponseInterface
{
    if (!$this->auth->check()) {
        return new RedirectResponse('/admin/login', HttpStatusCode::FOUND);
    }
    // ...
    return $handler->handle($request);
}

The class is registered in DI automatically; League Route creates the instance through the container.

5. Controller and FormRequest

// Controllers/AdminController.php
public function login(LoginRequest $request): ResponseInterface
{
    $loginDto = $request->toDto();
    if ($loginDto && $this->auth->attempt($loginDto->email, $loginDto->password, $loginDto->remember)) {
        return $this->response->redirectByName('admin.dashboard');
    }
    return $this->response->redirectByName('admin.login');
}

public function showLogin(): ResponseInterface
{
    return $this->viewResponse->create('@auth-admin/login');
}

LoginRequest — validation rules + DTO; CSRF and global HandleValidationExceptionMiddleware work like for any controller.

6. Views

{# Views/login.twig — namespace auth-admin, layout from App #}
{% extends '@dashboard/layouts/auth.twig' %}

<form method="post" action="{{ uri('admin.login.submit') }}">
    <input type="hidden" name="_csrf_token" value="{{ csrf_token }}">
    ...
</form>

Module templates — @auth-admin/...; shared admin chrome — @dashboard/... from resources/views/dashboard/.

7. Database

Migration in Database/Migrations/V2026_03_24_120000_CreateUsersTable.php, model UserModel, seeder UserSeeder.

php bin/console.php db:migrate
php bin/console.php db:seed -c "Concept\Components\AuthAdmin\Database\Seeders\UserSeeder"

For a single seeder specify the class full FQCN in quotes (option -c or --class). Short names like UserSeeder do not work. Without options, db:seed runs all registered seeders.

Paths from migrationPaths() and classes from seeders() are added to core registries on component boot.

8. Assets and CLI

php bin/console.php component:publish-assets
php bin/console.php component:list

More details — CLI and assets.

New component checklist

  1. {Name}Component.php implementing ComponentInterface
  2. Entry in config/components.php
  3. routes.php (if needed)
  4. Controllers, Services, Models, Requests/Dto
  5. Views + viewPaths() / viewContexts()
  6. Migrations / seeders / commands / assets — via interface methods
  7. db:migrate, component:publish-assets, route:list

Other reference components in the demo application: Acl (route interceptor + admin UI), SettingsManager (typed settings + Twig helpers), DebugBar (dev-only middleware and assets).