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á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
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 kryptomienOdoslaní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.