Rozdział 2: Fundament — User, bezpieczeństwo i użytkownicy
Zanim zbudujemy blog, potrzebujemy użytkowników. Artykuł ma autora, komentarz ma autora, a panel admina
wymaga logowania. Ten rozdział stawia wspólny fundament: encję User, konfigurację bezpieczeństwa i dane
testowe użytkowników. To on domyka łańcuch, którego wymaga BlogFixtures z Rozdziału 3.
Kolejność: ten rozdział zakłada działające środowisko z Rozdziału 1 (Docker,
make start). Jeśli masz już encjęUserw projekcie, potraktuj ten rozdział jako referencję. Budując od zera, wykonaj go przed encjami bloga (Rozdział 3).
Encja User
User implementuje dwa interfejsy Symfony Security: UserInterface (tożsamość i role) oraz
PasswordAuthenticatedUserInterface (hasło). Logujemy e-mailem, a nie loginem:
// src/Entity/User.php
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180)]
private ?string $email = null;
/** @var list<string> The user roles */
#[ORM\Column]
private array $roles = [];
/** @var string The hashed password */
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 100)]
private ?string $name = null;
#[ORM\Column]
private ?\DateTimeImmutable $createdAt = null;
#[ORM\Column(length: 64, unique: true, nullable: true)]
private ?string $apiToken = null;
}
Trzy metody wymagane przez interfejsy (poza getterami/setterami):
public function getUserIdentifier(): string
{
return (string) $this->email; // e-mail jednoznacznie identyfikuje użytkownika
}
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER'; // każdy zalogowany ma co najmniej ROLE_USER
return array_unique($roles);
}
public function eraseCredentials(): void
{
// wyczyść dane wrażliwe trzymane tymczasowo (tu: brak)
}
Warto zauważyć:
#[ORM\Table(name: 'user')]—userto słowo zarezerwowane w PostgreSQL, dlatego nazwa tabeli jest w backtickach.- Unikalny
email(UniqueConstraint) — to identyfikator logowania. rolesjakoarray(JSON w bazie) — prosta autoryzacja bez osobnej tabeli ról.apiToken(unikalny, nullable) — przyda się dopiero w Rozdziale 17 (uwierzytelnianie API tokenem); tu tylko deklarujemy pole.
Bez FOSUserBundle. Symfony 8 ma natywne bezpieczeństwo — encję generuje
make:user(docker compose exec app php bin/console make:user), a hasła hashuje wbudowany hasher. Zewnętrzny bundle nie jest potrzebny.
Konfiguracja bezpieczeństwa (security.yaml)
config/packages/security.yaml spina wszystko: hashowanie haseł, źródło użytkowników (provider),
firewalle i reguły dostępu:
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email # logowanie po e-mailu
firewalls:
dev:
pattern: ^/(_profiler|_wdt|assets|build)/
security: false
main:
lazy: true
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
default_target_path: app_dashboard
enable_csrf: true
logout:
path: app_logout
target: app_start
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/dashboard, roles: ROLE_USER }
Co tu jest ważne dla bloga:
password_hashers: 'auto'— Symfony sam wybiera najlepszy algorytm (bcrypt/argon) i sam weryfikuje hasła.providerz encji poemail— po zalogowaniu Symfony wczytaUserz bazy po adresie e-mail.form_login— logowanie przez formularz (/login), z ochroną CSRF.access_controlna^/admin— cały panel admina bloga (Rozdział 8) wymagaROLE_ADMIN.
Firewall
api(dla^/api) dokładamy dopiero w Rozdziale 17, razem z uwierzytelnianiem tokenem. Do Części I–IV wystarcza firewallmain.
Użytkownicy testowi (UserFixtures)
BlogFixtures (Rozdział 3) pobiera autora artykułu przez getReference('user_...'). Te referencje
rejestruje UserFixtures. Hasła hashujemy wstrzykniętym UserPasswordHasherInterface:
// src/DataFixtures/UserFixtures.php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserFixtures extends Fixture
{
public function __construct(
private UserPasswordHasherInterface $hasher,
) {}
public function load(ObjectManager $manager): void
{
$users = [
['name' => 'Admin Główny', 'email' => 'admin@example.com',
'password' => 'Admin1234!', 'roles' => ['ROLE_ADMIN'],
'apiToken' => 'dev-admin-api-token'],
['name' => 'Jan Kowalski', 'email' => 'jan.kowalski@example.com',
'password' => 'User1234!', 'roles' => []],
['name' => 'Piotr Wiśniewski', 'email' => 'piotr.wisniewski@example.com',
'password' => 'User1234!', 'roles' => ['ROLE_MODERATOR']],
];
foreach ($users as $data) {
$user = new User();
$user->setName($data['name']);
$user->setEmail($data['email']);
$user->setRoles($data['roles']);
$user->setPassword($this->hasher->hashPassword($user, $data['password']));
$user->setCreatedAt(new \DateTimeImmutable());
$user->setApiToken($data['apiToken'] ?? null);
$manager->persist($user);
// KLUCZOWE: referencja, po którą sięga BlogFixtures
$this->addReference('user_' . $data['email'], $user);
}
$manager->flush();
}
}
addReference('user_admin@example.com', $user) to most między fixturami: BlogFixtures
implementuje DependentFixtureInterface (zależy od UserFixtures) i pobiera admina przez
getReference('user_admin@example.com', User::class) — bez tworzenia go na nowo.
Konta, których użyjesz w tutorialu:
| Hasło | Rola | |
|---|---|---|
| admin@example.com | Admin1234! |
ROLE_ADMIN |
| jan.kowalski@example.com | User1234! |
ROLE_USER |
| piotr.wisniewski@example.com | User1234! |
ROLE_MODERATOR |
Migracja tabeli „user”
Encja gotowa — generujemy i uruchamiamy migrację, a potem ładujemy użytkowników:
docker compose exec app php bin/console make:migration
docker compose exec app php bin/console doctrine:migrations:migrate
docker compose exec app php bin/console doctrine:fixtures:load
Powstanie CREATE TABLE "user" (kolumny id, email, roles JSON, password, name, created_at,
api_token) z unikalnym indeksem na email. Sprawdź:
docker compose exec database psql -U app -d app -c '\d "user"'
Podsumowanie
Fundament pod blog stoi:
- ✅ encja
User(natywne interfejsy Security, logowanie e-mailem,roles,apiToken) — bez FOSUserBundle, - ✅
security.yaml:password_hashers: auto, provider z encji poemail, firewallmain(form_login+logout),access_controlna^/admin, - ✅
UserFixtureszaddReference()— most doBlogFixtures, - ✅ migracja tabeli
"user"i załadowani użytkownicy testowi.
W Rozdziale 3 zbudujemy encje bloga — BlogArticle, BlogCategory, BlogTag i BlogComment —
których autorzy i komentujący to właśnie utworzeni tu użytkownicy.