Docs LATEST

PageService

High-level API for loading, listing, and managing pages with caching and events.

Services

Namespace: VelvetCMS\Services\PageService


Definition #

class PageService
{
    public function __construct(
        ContentDriver $driver,
        EventDispatcher $events,
        CacheDriver $cache,
        CacheTagManager $cacheTags
    );

    public function load(string $slug): Page;
    public function save(Page $page): bool;
    public function list(array $filters = []): Collection;
    public function delete(string $slug): bool;
    public function exists(string $slug): bool;

    public function published(): Collection;
    public function drafts(): Collection;
    public function recent(int $limit = 5): Collection;
    public function count(): int;
}

Accessing PageService #

// Via container
$pages = app('pages');

// Via helper
$pages = pages();

Methods #

load() #

Load a page by slug. Results are cached for 300 seconds.

public function load(string $slug): Page
Parameter Type Description
$slug string The page slug

Returns: Page instance

Throws: NotFoundException if page doesn't exist

Events: page.loading, page.loaded

Example:

$pages = app('pages');

try {
    $page = $pages->load('welcome');
    echo $page->title;
    echo $page->content;
} catch (NotFoundException $e) {
    // Handle 404
}

save() #

Save a page (create or update). Automatically sets createdAt and updatedAt timestamps.

public function save(Page $page): bool
Parameter Type Description
$page Page The page to save

Returns: true on success

Events: page.saving, page.saved

Cache: Clears individual page cache and flushes list cache tag

Example:

$page = new Page();
$page->slug = 'about';
$page->title = 'About Us';
$page->content = '# About\n\nWelcome to our site.';
$page->status = 'published';

$pages->save($page);

Updating an existing page:

$page = $pages->load('about');
$page->title = 'About Our Company';
$page->content .= '\n\nUpdated content.';

$pages->save($page);

list() #

Get all pages matching filters. Results are cached using tags for 300 seconds.

public function list(array $filters = []): Collection
Parameter Type Description
$filters array Optional filter criteria

Returns: Collection of Page objects

Example:

// All pages
$all = $pages->list();

// With filters
$recent = $pages->list([
    'status' => 'published',
    'order_by' => 'updated_at',
    'order' => 'desc',
    'limit' => 10,
]);

delete() #

Delete a page by slug.

public function delete(string $slug): bool
Parameter Type Description
$slug string The page slug

Returns: true on success

Throws: NotFoundException if page doesn't exist

Events: page.deleting, page.deleted

Cache: Clears individual page cache and flushes list cache tag

Example:

try {
    $pages->delete('old-page');
} catch (NotFoundException $e) {
    // Page didn't exist
}

exists() #

Check if a page exists (not cached).

public function exists(string $slug): bool

Example:

if (!$pages->exists('welcome')) {
    $this->createDefaultPage();
}

published() #

Get all published pages.

public function published(): Collection

Example:

$publishedPages = $pages->published();

foreach ($publishedPages as $page) {
    echo $page->title . "\n";
}

drafts() #

Get all draft pages.

public function drafts(): Collection

Example:

$draftPages = $pages->drafts();
$this->info("You have {$draftPages->count()} drafts.");

recent() #

Get recently updated pages.

public function recent(int $limit = 5): Collection
Parameter Type Default Description
$limit int 5 Number of pages to return

Example:

$recentPages = $pages->recent(10);

count() #

Get total page count.

public function count(): int

Example:

$total = $pages->count();
echo "Total pages: {$total}";

Caching Behavior #

PageService implements intelligent caching:

Operation Cache Key TTL Behavior
load() page:{slug} 300s Individual page cached
list() pages:list:{hash} 300s Tagged with pages:list
save() - - Clears page:{slug}, flushes pages:list tag
delete() - - Clears page:{slug}, flushes pages:list tag

Example of cache invalidation:

// This is cached
$page = $pages->load('about'); // Cache HIT after first call

// Saving invalidates the cache
$page->title = 'New Title';
$pages->save($page);

// Next load fetches fresh data
$page = $pages->load('about'); // Cache MISS, fresh data

Events #

PageService dispatches these events:

Event Payload Description
page.loading string $slug Before loading a page
page.loaded Page $page After page is loaded
page.saving Page $page Before saving a page
page.saved Page $page After page is saved
page.deleting string $slug Before deleting a page
page.deleted string $slug After page is deleted

Example listeners:

$events = app('events');

// Log page views
$events->listen('page.loaded', function(Page $page) {
    app('analytics')->trackPageView($page->slug);
});

// Notify on publish
$events->listen('page.saved', function(Page $page) {
    if ($page->status === 'published') {
        app('notifications')->notifySubscribers($page);
    }
});

// Clear CDN on delete
$events->listen('page.deleted', function(string $slug) {
    app('cdn')->purge("/pages/{$slug}");
});

Usage Examples #

Controller Example #

class PageController
{
    public function __construct(
        private readonly PageService $pages
    ) {}

    public function show(Request $request, string $slug): Response
    {
        try {
            $page = $this->pages->load($slug);

            if ($page->status !== 'published') {
                throw new NotFoundException();
            }

            return Response::html(view('pages.show', [
                'page' => $page,
            ]));
        } catch (NotFoundException $e) {
            throw new NotFoundException("Page not found: {$slug}");
        }
    }

    public function index(): Response
    {
        $pages = $this->pages->published();

        return Response::html(view('pages.index', [
            'pages' => $pages,
        ]));
    }
}

Admin CRUD Example #

class AdminPageController
{
    public function __construct(
        private readonly PageService $pages
    ) {}

    public function store(Request $request): Response
    {
        $data = $request->validate([
            'slug' => 'required|string',
            'title' => 'required|string',
            'content' => 'required|string',
            'status' => 'in:draft,published',
        ]);

        if ($this->pages->exists($data['slug'])) {
            return Response::json([
                'error' => 'Page already exists',
            ], 409);
        }

        $page = new Page();
        $page->slug = $data['slug'];
        $page->title = $data['title'];
        $page->content = $data['content'];
        $page->status = $data['status'] ?? 'draft';

        $this->pages->save($page);

        return Response::json(['data' => $page], 201);
    }

    public function update(Request $request, string $slug): Response
    {
        $page = $this->pages->load($slug);

        $data = $request->validate([
            'title' => 'string',
            'content' => 'string',
            'status' => 'in:draft,published',
        ]);

        if (isset($data['title'])) {
            $page->title = $data['title'];
        }
        if (isset($data['content'])) {
            $page->content = $data['content'];
        }
        if (isset($data['status'])) {
            $page->status = $data['status'];
        }

        $this->pages->save($page);

        return Response::json(['data' => $page]);
    }

    public function destroy(string $slug): Response
    {
        $this->pages->delete($slug);

        return Response::json(['message' => 'Deleted']);
    }
}

CLI Command Example #

class ListPagesCommand extends Command
{
    public function __construct(
        private readonly PageService $pages
    ) {}

    public function signature(): string
    {
        return 'pages:list';
    }

    public function description(): string
    {
        return 'List all pages';
    }

    public function handle(): int
    {
        $status = $this->option('status');

        $pages = $status
            ? $this->pages->list(['status' => $status])
            : $this->pages->list();

        $this->table(
            ['Slug', 'Title', 'Status', 'Updated'],
            $pages->map(fn($p) => [
                $p->slug,
                $p->title,
                $p->status,
                $p->updatedAt?->format('Y-m-d H:i'),
            ])->toArray()
        );

        return 0;
    }
}