---
title: "DI, akcie a DTO — Posts for Laravel | Roundly"
description: "Namiesto fasády si vložte PostsManager alebo spustite jednu triedu akcie na každý zápis — s typovanými DTO pre controllery, joby a importy."
url: https://roundly-consulting.com/sk/open-source/navody/posts-for-laravel/akcie-a-dto
language: sk
---

[Všetky balíky](https://roundly-consulting.com/sk/open-source.md)

[Posts for Laravel](https://roundly-consulting.com/sk/open-source/navody/posts-for-laravel.md)

# DI, akcie a DTO

Fasáda je odporúčaná predvoľba, nie jediná cesta. Ten istý kód spúšťajú tri vstupné body:

- Fasáda Posts — najkratší zápis.
- Manažér vložený cez konštruktor — rovnaké API s explicitnou závislosťou a bez statických volaní. RoundlyConsulting\\Posts\\PostsManager je koreň fasády: singleton, zámerne nie final, aby ho fake mohol rozšíriť.
- Akcie — RoundlyConsulting\\Posts\\Actions\\\*Action, final readonly triedy s jedinou metódou execute(), ktoré zapojíte do vlastných akcií, jobov a príkazov.

## Vloženie manažéra

Posts::fake() rozširuje PostsManager a nahradí ho v kontajneri, takže fake dostanú aj vložené manažéry:

```php
use RoundlyConsulting\Posts\Models\Post;
use RoundlyConsulting\Posts\PostsManager;

final class PublishController
{
    public function __construct(private PostsManager $posts) {}

    public function __invoke(Post $post): Post
    {
        return $this->posts->publish($post);
    }
}

// The whole facade API is on the manager, builder included:
$this->posts->draft()->title('en', 'Injected')->publish();
$this->posts->publishDue();
```

## Spustenie akcie

Manažér nemá vlastnú logiku: každý zápis cez fasádu si z kontajnera vyžiada jednu akciu, takže vaša náhrada triedy akcie platí všade:

```php
use RoundlyConsulting\Posts\Actions\SchedulePostAction;
use RoundlyConsulting\Posts\Actions\UpdatePostSeoAction;
use RoundlyConsulting\Posts\DataTransferObjects\SeoData;

app(SchedulePostAction::class)->execute($post, now()->addDay());   // == Posts::schedule($post, $at)

app(UpdatePostSeoAction::class)->execute($post, new SeoData(ogImage: 'https://example.com/cover.jpg'));   // == Posts::seo()
```

## Metóda fasády → akcia

| Metóda fasády | Akcia | Čo robí |
| --- | --- | --- |
| `create(CreatePostData $data)` | `CreatePostAction` | Vytvorí nakonfigurovaný model; publikovanému bez dátumu nastaví teraz, naplánovaný vyžaduje dátum, pre tieto stavy spustí PostPublished / PostScheduled. |
| `publish(Post $post, ?CarbonInterface $at = null)` | `PublishPostAction` | Publikuje k $at ?? now(); PostPublished. Už publikovaný: bez zmeny, dátum zostane, žiadna udalosť. Pri archivovanom príspevku vyhodí výnimku. |
| `schedule(Post $post, CarbonInterface $at)` | `SchedulePostAction` | Naplánuje na $at; PostScheduled — vždy, keďže vždy posunie dátum. Rovnaká ochrana pri archivovanom. |
| `archive(Post $post)` | `ArchivePostAction` | Archivuje so zachovaným published\_at; PostArchived. Už archivovaný: bez zmeny. |
| `unpublish(Post $post)` | `UnpublishPostAction` | Späť na Draft z ľubovoľného stavu, published\_at sa vymaže; PostDrafted. Už koncept: bez zmeny. |
| `seo(Post $post, SeoData $data)` | `UpdatePostSeoAction` | setSeo() a potom save(). |
| `syncTags(Post $post, iterable $tags)` | `SyncPostTagsAction` | Reťazce sa nájdu alebo vytvoria podľa názvu v aktuálnom jazyku; tagy mimo zoznamu sa odpoja. |
| `publishDue()` | `PublishDuePostsAction` | Každý naplánovaný príspevok, ktorého čas nastal, si zamkne na úrovni riadka, publikuje ho k naplánovanému času a vráti ich počet. |

TransitionPostAction je @internal — skladajú ju akcie publikačného cyklu, z hostiteľského kódu ju preto nevolajte. draft(), findBySlug(), published() a query() za sebou žiadnu akciu nemajú.

## Vytvorenie z DTO

```php
use RoundlyConsulting\Posts\Actions\CreatePostAction;
use RoundlyConsulting\Posts\DataTransferObjects\CreatePostData;
use RoundlyConsulting\Posts\DataTransferObjects\CreatePostTranslationData;
use RoundlyConsulting\Posts\DataTransferObjects\SeoData;
use RoundlyConsulting\Posts\Enums\PostStatus;

$post = app(CreatePostAction::class)->execute(new CreatePostData(
    translations: [
        new CreatePostTranslationData(locale: 'en', title: 'Hello world', perex: 'A short summary', content: '<p>…</p>'),
        new CreatePostTranslationData(locale: 'sk', title: 'Ahoj svet', slug: 'Vlastny Slug'),
    ],
    status: PostStatus::Published,       // dated now when publishedAt is omitted; fires PostPublished
    authorType: $user->getMorphClass(),
    authorId: $user->getKey(),
    seo: new SeoData(canonical: 'https://example.com/hello-world'),
));
```

CreatePostAction vytvorí nakonfigurovaný model príspevku, použije každé preložené pole, ktoré nie je null, priradí autora len vtedy, keď sú zadané authorType aj authorId, uloží SEO balík a príspevok uloží. Príspevok vytvorený ako Published bez publishedAt dostane aktuálny dátum; príspevok v stave Scheduled musí publishedAt mať, inak sa vyhodí InvalidPostStatusTransitionException. Vytvorenie publikovaného alebo naplánovaného príspevku spustí PostPublished alebo PostScheduled.

SEO balík preberá len nepreložiteľné polia SeoData; meta nadpisy a popisy pre jednotlivé jazyky nastavte cez metaTitle a metaDescription v CreatePostTranslationData.

## DTO

Všetky tri sú final readonly triedy v RoundlyConsulting\\Posts\\DataTransferObjects:

```php
final readonly class CreatePostData
{
    public function __construct(
        public array $translations,                 // list<CreatePostTranslationData>
        public PostStatus $status = PostStatus::Draft,
        public ?CarbonInterface $publishedAt = null,
        public ?string $authorType = null,
        public int|string|null $authorId = null,
        public ?SeoData $seo = null,
    ) {}
}

final readonly class CreatePostTranslationData
{
    public function __construct(
        public string $locale,
        public ?string $title = null,
        public ?string $slug = null,                // manual slug: normalised + made unique
        public ?string $perex = null,
        public ?string $content = null,
        public ?string $metaTitle = null,
        public ?string $metaDescription = null,
    ) {}
}

final readonly class SeoData
{
    public function __construct(
        public ?string $metaTitle = null,
        public ?string $metaDescription = null,
        public ?string $canonical = null,
        public ?string $ogTitle = null,
        public ?string $ogDescription = null,
        public ?string $ogImage = null,
        public ?string $ogType = null,
        public ?string $twitterCard = null,
        public ?string $twitterSite = null,
        public ?string $twitterCreator = null,
        public ?string $robots = null,
        public ?string $ogSiteName = null,
    ) {}

    public static function fromBag(array $bag, ?string $metaTitle = null, ?string $metaDescription = null): self;
    public function toBag(): array;   // canonical, og_title, og_description, og_image, og_type,
                                      // twitter_card, twitter_site, twitter_creator, robots, og_site_name
}
```

Prázdne reťazce v uloženom balíku seo sa čítajú ako null.

## Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

[Ďalšie spôsoby podpory vrátane kryptomien](https://roundly-consulting.com/sk/podporte-nas.md)

Odoslaním daru súhlasíte s našimi [podmienkami prijímania darov](https://roundly-consulting.com/sk/podmienky-darov.md).

[Podporte našu open-source prácu (otvorí sa v novej karte)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Pridajte sa k nám na Patreone (otvorí sa v novej karte)](https://www.patreon.com/cw/roundly)

## Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.

[Ponuka do 48 hodín](https://roundly-consulting.com/sk/kontakt.md) [Prezrieť všetky balíky](https://roundly-consulting.com/sk/open-source/navody/posts-for-laravel.md)
