NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Posts for Laravel

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:

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:

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ádyAkciaČo robí
create(CreatePostData $data)CreatePostActionVytvorí 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)PublishPostActionPublikuje 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)SchedulePostActionNaplánuje na $at; PostScheduled — vždy, keďže vždy posunie dátum. Rovnaká ochrana pri archivovanom.
archive(Post $post)ArchivePostActionArchivuje so zachovaným published_at; PostArchived. Už archivovaný: bez zmeny.
unpublish(Post $post)UnpublishPostActionSpäť na Draft z ľubovoľného stavu, published_at sa vymaže; PostDrafted. Už koncept: bez zmeny.
seo(Post $post, SeoData $data)UpdatePostSeoActionsetSeo() a potom save().
syncTags(Post $post, iterable $tags)SyncPostTagsActionReťazce sa nájdu alebo vytvoria podľa názvu v aktuálnom jazyku; tagy mimo zoznamu sa odpoja.
publishDue()PublishDuePostsActionKaž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

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:

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

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

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.