September discount week
Tento týden až 80% sleva na e-learning týkající se jazyka C
50 % bodů zdarma na online výuku díky naší Slevové akci!

Lekce 1 - Rezervační systém v Symfony - Založení projektu a přihlášení

Zdravím všechny pokročilejší programátory v kurzu tutoriálů pro PHP framework Symfony.

Budeme zde programovat rezervační systém pro vozidla. K dispozici bude samozřejmě základní bezpečnost přihlášením emailem a heslem, uživatelské role (admin, běžný uživatel), přidávání vozidel, jejich úprava a mnoho dalšího.

Rezervace vozidel v Symfony

Na větším projektu nás potkají nové problémy, se kterými jsme se ještě nesetkali, a ukážeme si jejich řešení.

Správa uživatelů v Symfony

Pokud za sebou nemáte naše minulé lekce a nebo právě začínáte se Symfony, doporučuji se prvně podívat na kurz Základy frameworku Symfony.

Příprava projektu

Nejprve si připravíme celý projekt. Jelikož je vydána Symfony verze 5, založíme si projekt nad touto verzí. Začneme s požadavky pro tuto verzi Symfony. Nainstalujeme si PHP verzi 7.2.5 a vyšší. Dále budeme potřebovat composer pro instalaci balíků a samotného frameworku a samozřejmě databázi.

Symfony projekt

Pokud máme vše připraveno, vytvoříme si pro projekt složku a v ní zahájíme instalaci Symfony následujícím příkazem:

composer create-project symfony/website-skeleton:^5 rezervace_vozidel

Nyní se v aktuálním adresáři vytvořila složka s projektem, která má následující strukturu:

Struktura projektu

Po instalaci se můžeme ujistit, zda splňujeme požadavky příkazem:

symfony check:requirements

Jestli jste zvyklí využívat vlastní balík pro webový server (wamp/xampp), tak ho můžete využít. Symfony však může udělat svůj vlastní webový server, který není třeba dále konfigurovat, ten případně spustíme příkazem:

symfony server:start
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

V příkazovém řádku se dozvíme adresu s portem, kterou navštívíme ve webovém prohlížeči. Tím se zobrazí uvítací homepage Symfony.

Se zakládáním nového projektu byste neměli mít problém, případně opět doporučím nejdříve projít základní kurz, konkrétně vytvoření projektu v lekci Instalace Symfony a IDE.

Databáze

Nejprve řekneme Symfony kde je naše databáze a jak se k ní připojí. Otevřeme si soubor .env v root složce projektu a najdeme řádek:

DATABASE_URL=mysql://db_user:[email protected]:3306/db_name?serverVersion=5.7

Údaje na tomto řádku následně změníme na údaje našeho serveru. Nezapomeňte uvést verzi vašeho databázového serveru. V mém případě to bude uživatel root a heslo kokos123, verze MySQL 5.7:

DATABASE_URL=mysql://root:[email protected]:3306/itnetwork_tut?serverVersion=5.7

Pokud používáte databázi SQLite nebo PostgreSQL, URL je jiná! Dočtete se o tom o řádek výše v .env souboru.

Nyní si můžeme nechat vytvořit databázi projektu pomocí příkazu:

php bin/console doctrine:database:create

Přihlášení a uživatelé

Dalším základním kamenem aplikace jsou uživatelé.

Entita User

Nejprve si vytvoříme entitu User, která bude reprezentovat uživatele. Uděláme to pomocí příkazu:

php bin/console make:user

Ve vygenerovaném souboru src/Entity/User.php ještě přidáme uživateli vlastnosti $canReserve a $displayname. Soubor bude vypadat takto:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=180, unique=true)
     */
    private $email;

    /**
     * @ORM\Column(type="json")
     */
    private $roles = [];

    /**
     * @var string The hashed password
     * @ORM\Column(type="string")
     */
    private $password;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $displayname;

    /**
     * @ORM\Column(type="boolean")
     **/
    private $canReserve;


    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }


    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUsername(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    public function setRoles(array $roles): self
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getPassword(): string
    {
        return (string) $this->password;
    }

    public function setPassword(string $password): self
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function getSalt()
    {
        // not needed when using the "bcrypt" algorithm in security.yaml
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials()
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    public function getDisplayname(): ?string
    {
        return $this->displayname;
    }

    public function setDisplayname(?string $displayname): self
    {
        $this->displayname = $displayname;

        return $this;
    }

    public function getCanReserve(): ?bool
    {
        return $this->canReserve;
    }

    public function setCanReserve(bool $canReserve): self
    {
        $this->canReserve = $canReserve;

        return $this;
    }

}

Autentifikace

Ověřovat uživatele budeme pomocí emailu a hesla. Necháme si vytvořit také LoginFormAuthenticator (/logout, LoginForm.php a SecurityController.php) přes příkaz:

php bin/console make:auth

Všechny naše nové soubory jsou tedy následující:

src/Entity/User.php
src/Repository/UserRepository.php
src/Security/LoginFormAuthenticator.php
templates/security/login.html.twig

config/packages/security.yaml

Symfony si automaticky nakonfiguroval soubory config/packages/doctrine.yaml a config/packages/security.yaml, které jsme mu zadali do příkazového řádku. Avšak nikdo nemá zatím přístup k přihlášení na adrese /login. Na konec souboru security.yaml tedy přidáme řádek, který nám zajistí, že budou mít všichni přístup k přihlášení:

access_control:
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }

Migrace

Teď máme adresu /login "funkční". Nebudeme se ale moci přihlásit, protože nemáme zatím žádné uživatele a hlavně jsme neprovedli migraci databáze. Sice tedy máme entitu, ale v databázi reálně tabulka s uživateli ještě není. Pro migraci spustíme tyto dva příkazy:

php bin/console make:migration
php bin/console doctrine:migrations:migrate

Testování

Abychom projekt vyvíjeli trochu na úrovni, testovat uživatele (vytvářet "umělé entity") budeme pomocí fixtures, které využijeme i v budoucnu (např. pro rezervace). Nainstalujeme si je pomocí:

composer require --dev doctrine/doctrine-fixtures-bundle

a poté vytvoříme novou fixture:

php bin/console make:fixtures

Fixturu nazveme UserFixture, bude mít na starosti uživatele. Soubor src/DataFixtures/UserFixture.php upravíme do následující podoby:

<?php

namespace App\DataFixtures;

use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class UserFixture extends Fixture
{
    public const USER_USER_REFERENCE = 'user-user';
    public const USER_ADMIN_REFERENCE = 'user-admin';

    private $encoder;

    public function __construct(UserPasswordEncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }

    public function load(ObjectManager $manager)
    {
        $user1 = new User();
        $user1->setPassword($this->encoder->encodePassword($user1, "kokos1"));
        $user1->setCanReserve(0); // otestujeme i to, že admin bude ignorovat tuto vlastnost
        $user1->setDisplayname("Admin");
        $user1->setEmail("[email protected]");
        $user1->setRoles(["ROLE_ADMIN"]);

        $user2 = new User();
        $user2->setPassword($this->encoder->encodePassword($user2, "kokos1"));
        $user2->setCanReserve(0);
        $user2->setDisplayname("Test");
        $user2->setEmail("[email protected]");
        $user2->setRoles(["ROLE_USER"]);

        // díky tomuto se pak dostaneme k těmto uživatelům z jiných fixtur
        $this->addReference(self::USER_ADMIN_REFERENCE, $user1);
        $this->addReference(self::USER_USER_REFERENCE, $user2);

        $manager->persist($user1);
        $manager->persist($user2);

        $manager->flush();
    }
}

Fixtura vytvoří v databázi 2 testovací uživatele. Určitě jste si všimli, že jeden je administrátor a druhý jen běžný uživatel. To abychom si mohli ověřit, že funkce aplikace pro administrátora fungují a zároveň nejsou dostupné pro běžného uživatele. To je konec konců velmi důležité!

Nakonec spustíme a potvrdíme příkaz pro nahrání uživatelů do databáze:

php bin/console doctrine:fixtures:load

Závěr

Nyní se můžeme přihlásit na adrese /login :) Vyhodí nám to chybu "TODO: provide a valid redirect inside". Zatím se nemáme kam přihlásit, proto budeme myslet do budoucna a odkážeme na budoucí routu /myBooking v metodě onAuthenticationSuccess v souboru src/Security/LoginFormAuthenticator.php následovně:

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
        return new RedirectResponse($targetPath);
    }

    // TODO: redirect after authentication For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
    return new RedirectResponse($this->urlGenerator->generate("myBooking"));
}

Tady ukončíme dnešní lekci.

V další lekci, Rezervační systém v Symfony - Entity, repositáře, kontrolery, si vytvoříme entity rezervace, vozidla, kontroler a upravíme repositář pro rezervace.


 

Všechny články v sekci
Rezervační systém pro vozidla v Symfony
Článek pro vás napsal Samuel Hél
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Autor se věnuje hlavně programování, nejvíce z oblasti webových technologií, dělá občasné video edity ze svých dovolených. Má rád memes a svou gf
Aktivity (12)

 

 

Komentáře

Avatar
Tomáš Daněk:26. května 0:26

Po přihlášení se zobrazí chybové hlášení :(
Myslím si, že by bylo vhodné v tomto bodě ještě vytvořit např. "User" kontroler příkazem

php bin/console make:controller

a upravit LoginFormAuthen­ticator.php tak, aby přihlášení proběhlo korektně. Byla by to taková pěkná tečka za první lekcí...

 
Odpovědět
26. května 0:26
Avatar
Samuel Hél
Tým ITnetwork
Avatar
Odpovídá na Tomáš Daněk
Samuel Hél:26. května 10:26

Ahoj,
ano, po přihlášení se zobrazí chybové hlášení, raději to napíšu ještě do lekce. Kontroler "User" se dělá až v příští lekci. Konec lekce jsem ještě upravil, ať si tam rovnou přidáme budoucí routu /myBooking, ať se v další lekci může každý po přihlášení správně přesměrovat.

 
Odpovědět
26. května 10:26
Avatar
Vojtěch Janoušek:9. června 14:57

Ahoj, dostavam chybu:

C:\xampp\htdocs\rezervace_vozidel>php bin/console doctrine:fixtures:load

 Careful, database "itnetwork_tut" will be purged. Do you want to continue? (yes/no) [no]:
 > y

   > purging database
   > loading App\DataFixtures\AppFixtures
   > loading App\DataFixtures\UserFixture

In AbstractMySQLDriver.php line 55:

  An exception occurred while executing 'INSERT INTO user (email, roles, password, displayname, can_reserve) VALUES (?, ?, ?, ?, ?)' with params ["[email protected]", "[\"ROL
  E_USER\"]", "$argon2id$v=19$m=65536,t=4,p=1$WVpaUjc2aUp4bHpRMDAuSA$T3NSDDT3n\/7e2DTN1rBhj97eLj\/5chQI3I4v8FPw29g", "Test", 0]:

  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '0' for key 'UNIQ_8D93D6496BFA09B'


In PDOStatement.php line 129:

  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '0' for key 'UNIQ_8D93D6496BFA09B'


In PDOStatement.php line 127:

  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '0' for key 'UNIQ_8D93D6496BFA09B'


doctrine:fixtures:load [--append] [--group GROUP] [--em EM] [--shard SHARD] [--purge-with-truncate] [-h|--help] [-q|--quiet] [-v|vv|vvv|--verbose] [-V|--version] [--ansi] [--no-ansi] [-n|--no-interaction] [-e|--env ENV] [--no-debug] [--] <command>

Mohu vas pozadat o radu? diky

 
Odpovědět
9. června 14:57
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Samuel Hél
Tým ITnetwork
Avatar
Odpovídá na Vojtěch Janoušek
Samuel Hél:9. června 15:12

Ahoj,
v databázi už asi nějaké data jsou a nechcou se přepsat. Zkus příkaz spustit s parametrem --purge-with-truncate takto:

php bin/console doctrine:fixtures:load --purge-with-truncate
 
Odpovědět
9. června 15:12
Avatar
Vojtěch Janoušek:9. června 15:25

Ahoj, v DB je jen admin user. User Test spadne

 
Odpovědět
9. června 15:25
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 5 zpráv z 5.