Halloweenská akce! Na stránce s dobitím bodů zadej dole kód STRASIDELNYCH20 a získej porci +20% bodů zdarma!
Akce končí 31.10. o půlnoci.

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

PHP Symfony Základy Jednoduchý redakční systém v Symfony - Model článků

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

Vítám vás všechny u pokračování kurzu tutoriálů o tvorbě webových aplikací v PHP frameworku Symfony. V minulé lekci, Jednoduchý redakční systém v Symfony - Struktura projektu, jsme si připravili projektovou strukturu pro jednoduchý redakční systém a jak jsem slíbil, dnes se rovnou bez zbytečných řečí vrhneme na implementaci. Takže jdeme na to! :)

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/Ar­ticle.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. Naše třída entity článků bude tedy vypadat takto:

<?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;
    }
}

Třídu si můžete nechat i automaticky vygenerovat příkazem php bin/console make:entity, což je velice praktické a šetří to čas :)

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í např. když budeme 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:

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

Databázi poté nemusíte 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

Pokud jste vše udělali dobře, měli byste v nastavené databázi vidět novou tabulku article.

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 vaší 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/Ar­ticleReposito­ry.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 Symfony\Bridge\Doctrine\RegistryInterface;

/**
 * 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(RegistryInterface $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 jste si nechali generovat třídu entity článku, budete mít vygenerovanou i základní kostru jejího repositáře, kterou pouze upravíte.

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. Tím náš projekt zprovozníme :)


 

 

Článek pro vás napsal Jindřich Máca
Avatar
Jak se ti líbí článek?
3 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 (10)

 

 

Komentáře

Avatar
josef.nuhlicek:22.6.2017 15:09

Děkuji za velice podnětný seriál. Jenom se zeptám, k tomu generování struktury tabulky z konzole, neměl by tam být spíš příkaz

php bin/console doctrine:schema:update --force

Protože po tom příkazu, co je uveden v článku, to pouze vypíše nápovědu, a bez toho ... help ... to zase pouze vytváří novou databázi, bez tabulky...

 
Odpovědět 22.6.2017 15:09
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na josef.nuhlicek
Jindřich Máca:22.6.2017 21:00

V první řadě jsem rád, že se seriál líbí. :)

Co se týče příkazů, tak to help byl rozhodně překlep, který jsem hned opravil. Ani nevím, jak se to tam dostalo. :D

Jinak tedy příkaz

php bin/console doctrine:database:create

se používá pro vytvoření celé nové databáze podle Symfony konfigurace a příkaz

php bin/console doctrine:schema:update --force

se pak používá pro synchronizaci aktuálních entit se schématem dané databáze.

Takže děkuji za dobrou připomínku, hned jsem to do seriálu také doplnil.

Každopádně více informací můžete nalézt samozřejmě přímo v oficiální dokumentaci - http://symfony.com/…octrine.html ;)

 
Odpovědět 22.6.2017 21:00
Avatar
Tomáš Daněk:26. září 9:01

Příkaz

php bin/console doctrine:generate:entity

není definován. Pokud použiji příkaz

php bin/console doctrine:generate:entities

tak nevím jaké argumenty mám zadat... Jak si tedy mohu nechat automaticky vygenerovat třídu?

 
Odpovědět 26. září 9:01
Avatar
Tomáš Daněk:26. září 16:07

Nestačilo by pro automatické vygenerování třídy použít raději:

bin/consle make:entity
 
Odpovědět 26. září 16:07
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Tomáš Daněk
Jindřich Máca:26. září 18:55

Ahoj, máš pravdu, v článku byla chyba. V novém Symfony je to skutečně příkaz php bin/console make:entity, viz. oficiální dokumentace. V článku už jsem to také opravil. ;)

 
Odpovědět 26. září 18:55
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.