Building your first feature (Controller + Form + Database)

Dokumentacja jest stale rozwijana i aktualizowana wraz z postępem projektu. Ponieważ powstaje ona równolegle z kodem, niektóre sekcje mogą tymczasowo odnosić się do wcześniejszych wersji systemu - są one sukcesywnie dostosowywane do bieżącego stanu aplikacji. Jeśli zauważysz nieaktualne informacje, zgłoś to lub załóż Issue / Pull Request.

Dokumentacja

# Building your first feature (Controller + Form + Database)

Ten przewodnik pokazuje, jak stworzyć kompletną funkcjonalność w DBM Framework:

  • kontroler (HTTP)
  • formularz (walidacja)
  • serwis (logika biznesowa)
  • repozytorium (baza danych)

Całość oparta o system DI (autowiring + konstruktor).


# Struktura

Przykładowy moduł:

App\Authentication\
├── Controller/
├── Form/
├── Repository/
├── Service/

# 1. Kontroler

Kontroler odpowiada za obsługę requestów i delegowanie logiki.

class AuthenticationController extends BaseController
{
    public function __construct(
        private readonly AuthenticationService $service,
        private readonly AuthenticationRepository $repository,
        private readonly TranslationInterface $translation,
        private readonly CsrfTokenManager $csrf,
        private readonly FlashBag $flash,
        private readonly Logger $logger
    ) {}

    /**
     * @routing GET '/login' name: login
     */
    public function login(): ResponseInterface
    {
        return $this->render('authentication/login.phtml', [
            'meta' => $this->service->getMetaLogin(),
        ]);
    }

    /**
     * @routing POST '/login/signin' name: login_signin
     */
    public function loginSignin(): ResponseInterface
    {
        $formData = (array) $this->request->getParsedBody();

        $form = new AuthenticationLoginForm(
            $this->csrf,
            $this->translation
        );

        $errors = $form->validate($formData);

        if ($errors) {
            return $this->render('authentication/login.phtml', [
                'validate' => $errors,
            ]);
        }

        $userId = $this->service->authenticateUser(
            $formData['dbm_login'],
            $formData['dbm_password']
        );

        if (!$userId) {
            $this->flash->set(
                $this->translation->trans('auth.login.invalid'),
                'messageWarning'
            );

            return $this->redirect('login');
        }

        $this->setSession(getenv('APP_SESSION_KEY'), $userId);

        $this->flash->set(
            $this->translation->trans('auth.login.success'),
            'messageSuccess'
        );

        return $this->redirect('account');
    }
}

# 2. Formularz i walidacja

Formularz odpowiada za walidację danych wejściowych.

class AuthenticationLoginForm extends Validator
{
    public function __construct(
        CsrfTokenManager $csrf,
        protected ?TranslationInterface $translation = null
    ) {
        parent::__construct($translation);
        $this->registerCsrfRule($csrf);
    }

    public function validate(array $data): array
    {
        $this->rules([
            'csrf_token' => ['required', 'csrf'],
            'dbm_login' => ['required', 'string', 'min:2', 'max:60'],
            'dbm_password' => ['required', 'string', 'min:6'],
        ], $data);

        return $this->getErrors();
    }
}

# Walidacja rozszerzona (custom)

Możesz dodać dodatkową logikę:

if ($this->repository->checkLogin($data['dbm_login'])) {
    $this->registerError(
        'dbm_login',
        $this->translation->trans('auth.login.exists')
    );
}

# 3. Serwis (logika biznesowa)

Serwis zawiera logikę aplikacji — NIE kontroler.

class AuthenticationService
{
    public function __construct(
        private readonly AuthenticationRepository $repository,
        private readonly TranslationInterface $translation,
        private readonly Logger $logger
    ) {}

    public function authenticateUser(string $login, string $password): ?int
    {
        $result = $this->repository->checkIsUserCorrect([
            'login' => $login,
            'email' => $login,
        ], $password);

        if (!is_numeric($result)) {
            return null;
        }

        return (int) $result;
    }
}

# 4. Repozytorium (baza danych)

Repozytorium komunikuje się z bazą danych.

class AuthenticationRepository extends AbstractRepository
{
    protected string $table = 'dbm_user';

    public function checkLogin(string $login): bool
    {
        return $this->database->fetch(
            "SELECT 1 FROM dbm_user WHERE login = :login",
            ['login' => $login]
        ) !== null;
    }
}

# 5. Flow działania

Schemat działania:

Request → Controller → Form (Validation)
                    ↓
                Service
                    ↓
               Repository
                    ↓
                Database

# 6. Flash messages

$this->flash->set('Zalogowano poprawnie', 'messageSuccess');

W szablonie:

{% if ($flash = $this->getFlash()): %}
    <div>{{ $flash }}</div>
{% endif %}

# 7. Tłumaczenia

$this->translation->trans('auth.login.success');

Plik:

translations/pl/validation.php
return [
    'auth.login.success' => 'Zalogowano poprawnie',
];

# 8. CSRF

<input type="hidden" name="csrf_token" value="{{ $this->csrf() }}">

Walidacja:

'csrf_token' => ['required', 'csrf']

# 9. Najważniejsze zasady

  • Kontroler = tylko flow HTTP
  • Serwis = logika biznesowa
  • Formularz = walidacja
  • Repozytorium = baza danych

# 10. Podsumowanie

DBM Framework stosuje podejście:

  • czysty konstruktor (DI)
  • brak bazy w kontrolerze
  • separacja odpowiedzialności

Dzięki temu kod jest:

  • czytelny
  • testowalny
  • łatwy do rozwijania

To jest podstawowy wzorzec budowania funkcjonalności w DBM Framework.