IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 6 - Jednoduchý redakční systém v Symfony - Model článků

V minulé lekci, Jednoduchý redakční systém v Symfony - Struktura projektu, jsme si připravili projektovou strukturu pro jednoduchý redakční systém.

Dnes se vrhneme na třídu a repozitář.

Databáze

V první řadě se podíváme na nastavení i obsah databáze. V Symfony se většinou pro práci s databází používá knihovna třetích stran Doctrine. Ta pracuje s principem Objektově Relačního Mapování (ORM), jehož základ tvoří entity. S jednou takovou entitou jsme se již setkali v lekci kurzu o tvorbě operace kalkulačky.

src/Entity/Article.php

Nyní si obdobně vytvoříme entitu reprezentující článek v našem redakčním systému, následně ji obohatíme o anotace z Doctrine knihovny a necháme si podle ní vygenerovat celou relační databázovou strukturu. Doctrine potom zajistí, že položky z naší databáze v dané tabulce se automaticky namapují na instance oné entitní třídy (tedy na objekty), což je právě princip celého ORM.

Třídu si můžeme nechat vygenerovat příkazem:

php bin/console make:entity

Název entity bude Article. Poté se konzole ptá, jaké vlastnosti (atributy) bude entita mít, můžete si to sami nastavit podle entity níže (atribut $id je vygenerován vždy automaticky a má ho každá vygenerovaná entita). Každopádně se nám vygeneruje automaticky i repozitář, takže to ušetří práci. Entitu tedy budeme mít v následující podobě:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Reprezentuje záznamy databázové tabulky článků v redakčním systému.
 * @ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
 * @UniqueEntity("url", message="Článek s touto URL adresou již existuje!")
 * @package App\Entity
 */
class Article
{
    /**
     * @var int Unikátní ID článku.
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @var string Titulek článku.
     * @ORM\Column(type="string")
     * @Assert\NotBlank(message="Titulek článku nemůže být prázdný!")
     */
    private $title;

    /**
     * @var string Text (obsah) článku.
     * @ORM\Column(type="text")
     * @Assert\NotBlank(message="Obsah článku nemůže být prázdný!")
     */
    private $content;

    /**
     * @var string Unikátní URL adresa článku.
     * @ORM\Column(type="string", unique=true)
     * @Assert\NotBlank(message="URL adresa článku nemůže být prázdná!")
     */
    private $url;

    /**
     * @var string Krátký popis článku.
     * @ORM\Column(type="string")
     * @Assert\NotBlank(message="Popis článku nemůže být prázdný!")
     */
    private $description;

    /**
     * Getter pro ID článku.
     * @return int ID článku
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * Getter pro titulek článku.
     * @return null|string titulek článku
     */
    public function getTitle(): ?string
    {
        return $this->title;
    }

    /**
     * Setter pro titulek článku.
     * @param string|null $title titulek článku
     * @return Article sebe
     */
    public function setTitle(string $title = null): self
    {
        $this->title = $title;
        return $this;
    }

    /**
     * Getter pro obsah článku.
     * @return null|string obsah článku
     */
    public function getContent(): ?string
    {
        return $this->content;
    }

    /**
     * Setter pro obsah článku.
     * @param string|null $content obsah článku
     * @return Article sebe
     */
    public function setContent(string $content = null): self
    {
        $this->content = $content;
        return $this;
    }

    /**
     * Getter pro URL adresu článku.
     * @return null|string URL adresa článku
     */
    public function getUrl(): ?string
    {
        return $this->url;
    }

    /**
     * Setter pro URL adresu článku.
     * @param string|null $url URL adresa článku
     * @return Article sebe
     */
    public function setUrl(string $url = null): self
    {
        $this->url = $url;
        return $this;
    }

    /**
     * Getter pro popis článku.
     * @return null|string popis článku
     */
    public function getDescription(): ?string
    {
        return $this->description;
    }

    /**
     * Setter pro popis článku.
     * @param string|null $description popis článku
     * @return Article sebe
     */
    public function setDescription(string $description = null): self
    {
        $this->description = $description;
        return $this;
    }
}

Všimněte si zde hlavně nových ORM anotací, které "transformují" onu třídu na příslušnou databázovou tabulku a její atributy na sloupce oné tabulky s daným typem a vlastnostmi.

Za zmínku také stojí anotace @UniqueEntity, která v kombinaci s unikátní URL článku zajistí, že taková entita se vyskytne pouze jednou. To se hodí, když budeme například pomocí formuláře vytvářet nové.

Když již zmiňujeme formuláře, dále si můžete povšimnout, že jsme zde rovnou připravili i anotace, které využijeme při tvorbě formuláře pro práci s články, který tím pádem můžeme také vytvořit na základě této entity ;)

Nastavení přístupových údajů a vytvoření databáze

Abychom si nyní mohli nechat vygenerovat databázovou strukturu, musíme ještě nastavit přístupové údaje do naší databáze. To uděláme tak, že upravíme hodnotu proměnné DATABASE_URL uvnitř souboru .env v kořenové složce:

...
DATABASE_URL=mysql://<uživatelské jméno>:<heslo>@localhost:3306/<název databáze>?serverVersion=5.7
...

Databázi poté nemusíme vytvářet ručně, ale pro její vytvoření rovnou použít Doctrine příkaz:

php bin/console doctrine:database:create

Generování databázové struktury

Pro vygenerování naší relační databázové struktury v podobě jedné tabulky článků opět použijeme Doctrine příkazy.

Nejdříve vytvoříme novou tzv. migraci pomocí příkazu:

php bin/console make:migration

Tento příkaz vytvoří PHP skript ve složce src/Migrations/, kde zafixuje aktuální databázovou strukturu. To je výhodné zejména pokud bychom ji do budoucna dále měnili, jelikož další migrační skripty mohou vycházet z předchozí verze a pouze aplikovat změny. :-`

Následujícím příkazem poté aplikujeme všechny migrační změny:

php bin/console doctrine:migrations:migrate

Může se nás to zeptat, jestli chceme migraci určitě provést (můžou se ztratit nějaké data či měnit schéma). Jelikož databázi máme úplně novou, nemáme se ani o co bát a stačí migraci odsouhlasit pomocí slovíčka yes a potvrzení enterem.

Pokud jsme vše udělali dobře, měli bychom v databázi vidět novou tabulku article a doctrine_migration_versions. Druhá tabulka obsahuje informace o migracích a tabulku bychom neměli nijak měnit či mazat.

Pokud si chcete vypsat všechny dostupné Doctrine příkazy a jejich popis, použijte příkaz php bin/console list doctrine

Předpřipravené články

My si do naší tabulky článků doplníme ještě dva předpřipravené články, jeden pro úvodní stránku a jeden pro vypsání chyby. To bývá standardem u většiny redakčních systémů.

Podle oficiálního postupu bych si pro tyto články měl připravit tzv. Doctrine Fixture. To by ale vyžadovalo instalaci externího bundlu DoctrineFixtu­resBundle a toho vás chci prozatím ušetřit. Vyřešil jsem to tedy pouhým SQL skriptem, který stačí spustit nad naší databází:

INSERT INTO `article` VALUES ('1', 'Úvod', '<p>Vítejte na našem webu!</p>\r\n\r\n<p>Tento web je postaven na <strong>jednoduchém redakčním systému v Symfony frameworku</strong>. Toto je úvodní článek, načtený z databáze.</p>', 'uvod', 'Úvodní článek na webu v Symfony v PHP');
INSERT INTO `article` VALUES ('2', 'Stránka nebyla nalezena', '<p>Litujeme, ale požadovaná stránka nebyla nalezena. Zkontrolujte prosím URL adresu.</p>', 'chyba', 'Stránka nebyla nalezena.');

Model

Přesuneme se k vytváření modelu. Třídám v modelové vrstvě, které mají pracovat s Doctrine entitami, se říká repositáře. To je trochu rozdíl oproti modelu kalkulačky, ale v podstatě je pouze kosmetický.

src/Repository/ArticleRepository.php

Vytvořme si tedy nyní repositář pro správu článků:

<?php

namespace App\Repository;

use App\Entity\Article;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\ORMException;
use Doctrine\Persistence\ManagerRegistry;

/**
 * Repositář pro správu článků v redakčním systému.
 * @method Article|null find($id, $lockMode = null, $lockVersion = null)
 * @method Article|null findOneBy(array $criteria, array $orderBy = null)
 * @method Article[]    findAll()
 * @method Article[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 * @package App\Repository
 */
class ArticleRepository extends ServiceEntityRepository
{
    /** @inheritdoc */
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Article::class);
    }

    /**
     * Vrátí článek z databáze podle jeho URL.
     * @param string $url URl článku
     * @return Article|null první článek, který má danou URL nebo null pokud takový neexistuje
     */
    public function findOneByUrl(string $url): ?Article
    {
        return $this->findOneBy(['url' => $url]);
    }

    /**
     * Uloží článek do systému.
     * Pokud není nastaveno ID, vloží nový, jinak provede editaci.
     * @param Article $article článek
     * @throws ORMException Jestliže nastane chyba při ukládání článku.
     */
    public function save(Article $article): void
    {
        $this->getEntityManager()->persist($article);
        $this->getEntityManager()->flush($article);
    }

    /**
     * Odstraní článek s danou URL.
     * @param string $url URL článku
     * @throws ORMException Jestliže nastane chyba při mazání článku.
     */
    public function removeByUrl(string $url): void
    {
        if ($article = $this->findOneByUrl($url)) {
            $this->getEntityManager()->remove($article);
            $this->getEntityManager()->flush();
        }
    }
}

Pokud jsme si nechali generovat třídu entity článku, budeme mít vygenerovanou i základní kostru jejího repositáře, kterou nyní pouze upravíme.

Důležité je zmínit, že každý repositář by měl dědit ze třídy ServiceEntityRepository, která mu umožňuje pracovat s entitami, vybírat je a ukládat i mazat je z databáze. Každá instance třídy Article pak reprezentuje jeden záznam v databázi.

Co stojí za povšimnutí je, že ukládání nebo mazání entity bychom měli ještě potvrdit voláním flush(). Do té doby se totiž fyzicky neprovede SQL příkaz nad databází.

To je z dnešní lekce vše.

Příště, v lekci Jednoduchý redakční systém v Symfony - Výpis článku, se budeme věnovat kontrolerům a šablonám.


 

Měl jsi s čímkoli problém? Zdrojový kód vzorové aplikace je ke stažení každých pár lekcí. Zatím pokračuj dál, a pak si svou aplikaci porovnej se vzorem a snadno oprav.

Předchozí článek
Jednoduchý redakční systém v Symfony - Struktura projektu
Všechny články v sekci
Základy frameworku Symfony pro PHP
Přeskočit článek
(nedoporučujeme)
Jednoduchý redakční systém v Symfony - Výpis článku
Článek pro vás napsal Jindřich Máca
Avatar
Uživatelské hodnocení:
23 hlasů
Autor se věnuje převážně webovým technologiím, ale má velkou zálibu ve všem vědeckém, nejen ze světa IT. :-)
Aktivity