PHPackages                             marko/blog - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. marko/blog

AbandonedArchivedLibrary[Framework](/categories/framework)

marko/blog
==========

Blog module for Marko Framework

0.1.3(2mo ago)020MITPHPPHP ^8.5

Since Mar 25Pushed 2mo agoCompare

[ Source](https://github.com/marko-php/marko-blog)[ Packagist](https://packagist.org/packages/marko/blog)[ RSS](/packages/marko-blog/feed)WikiDiscussions develop Synced 3w ago

READMEChangelogDependencies (20)Versions (8)Used By (0)

marko/blog
==========

[](#markoblog)

WordPress-like blog for Marko — posts, authors, categories, tags, and threaded comments with email verification.

Installation
------------

[](#installation)

```
composer require marko/blog
```

**Required:** A view driver (e.g., `marko/view-latte`) and a database driver (e.g., `marko/database-mysql`):

```
composer require marko/blog marko/view-latte marko/database-mysql
```

Quick Example
-------------

[](#quick-example)

Once installed with a view and database driver, the blog works automatically:

1. Run migrations to create tables: `marko db:migrate`
2. Visit `/blog` to see the post list
3. Visit `/blog/{slug}` to view a single post

View Templates
--------------

[](#view-templates)

The blog package ships with Latte templates (via `marko/view-latte`) but supports any template engine.

### Overriding Templates

[](#overriding-templates)

You can override any blog template in your app module by creating a matching path under `app/views/blog/`. The app module's view path takes precedence, allowing full control over rendering without modifying the package.

For example, to override the post list template:

```
app/views/blog/post/list.latte

```

### Alternative View Engines

[](#alternative-view-engines)

To use a different template engine such as Blade or Twig, install the corresponding view driver package and register your own view implementations via Preferences. Any view driver implementing the view contract works.

Configuration
-------------

[](#configuration)

Publish or set the following keys in your config:

KeyDefaultDescription`posts_per_page``10`Number of posts shown per page`comment_max_depth``3`Maximum thread depth for nested comments`comment_rate_limit_seconds``60`Minimum seconds between comments from one user`verification_token_expiry_days``7`Days before a comment verification token expires`route_prefix``/blog`URL prefix for all blog routesExtensibility
-------------

[](#extensibility)

### Preferences (Swap Implementations)

[](#preferences-swap-implementations)

Use the `#[Preference]` attribute to swap any blog class with your own implementation. This lets you replace or override core behavior without forking:

```
use Marko\Core\Attributes\Preference;

#[Preference(PostRepositoryInterface::class)]
class MyPostRepository implements PostRepositoryInterface
{
    // custom implementation
}
```

### Plugins (Hook Methods)

[](#plugins-hook-methods)

Use `#[Plugin]` with `#[Before]` and `#[After]` attributes to hook into any public method without overriding the entire class:

```
use Marko\Core\Attributes\Plugin;
use Marko\Core\Attributes\Before;
use Marko\Core\Attributes\After;

#[Plugin(target: PostService::class)]
class PostServicePlugin
{
    /**
     * The method name matches the target method. #[Before] determines timing.
     * Return null to pass through, an array to modify arguments, or a non-null
     * non-array value to short-circuit and skip the original method.
     */
    #[Before]
    public function createPost(array $data): null|array
    {
        // Modify the data before createPost runs by returning an array
        $data['slug'] = strtolower(trim($data['slug']));

        return [$data];
    }
}

#[Plugin(target: PostService::class)]
class PostServiceAuditPlugin
{
    #[After]
    public function createPost(Post $post): Post
    {
        // Act on the result after createPost runs
        return $post;
    }
}
```

### Observers (React to Events)

[](#observers-react-to-events)

Use `#[Observer]` to react to blog lifecycle events without modifying core classes:

```
use Marko\Core\Attributes\Observer;
use Marko\Blog\Events\Post\PostCreated;

#[Observer(event: PostCreated::class)]
class PostCreatedObserver
{
    public function handle(PostCreated $event): void
    {
        // react to event — send notification, update cache, etc.
    }
}
```

Observers are ideal for event-driven side effects such as sending emails, clearing caches, or syncing to external services. Use an observer for every event reaction you need to add.

Available Events
----------------

[](#available-events)

The blog dispatches the following lifecycle events:

EventTrigger`PostCreated`A new post is created`PostUpdated`A post is updated`PostPublished`A post is published`PostDeleted`A post is deleted`CommentCreated`A new comment is submitted`CommentVerified`A comment email is verified`CommentDeleted`A comment is deleted`CategoryCreated`A new category is created`TagCreated`A new tag is created`AuthorCreated`A new author is createdRoutes
------

[](#routes)

The blog registers the following public routes (prefix configurable via `route_prefix`):

MethodPathDescription`GET /blog`Post listPaginated list of published posts`GET /blog/{slug}`Post detailSingle post view`GET /blog/category/{slug}`Category archivePosts filtered by category`GET /blog/tag/{slug}`Tag archivePosts filtered by tag`GET /blog/author/{slug}`Author archivePosts filtered by author`GET /blog/search`Search resultsFull-text post search`POST /blog/{slug}/comment`Submit commentSubmit a comment on a post`GET /blog/comment/verify/{token}`Verify commentVerify a comment via email tokenCLI Commands
------------

[](#cli-commands)

CommandDescription`blog:publish-scheduled`Publish any posts whose scheduled publish date has passed`blog:cleanup`Remove expired verification tokens and other stale dataDocumentation
-------------

[](#documentation)

Full usage, configuration, events, API reference, and examples: [marko/blog](https://marko.build/docs/packages/blog/)

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance85

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

6

Last Release

79d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/437029?v=4)[Mark Shust](/maintainers/markshust)[@markshust](https://github.com/markshust)

---

Top Contributors

[![markshust](https://avatars.githubusercontent.com/u/437029?v=4)](https://github.com/markshust "markshust (67 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/marko-blog/health.svg)

```
[![Health](https://phpackages.com/badges/marko-blog/health.svg)](https://phpackages.com/packages/marko-blog)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
