Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Recent Posts Block Extension Laravel Package

anomaly/recent_posts_block-extension

An extension for the Recent Posts block in Anomaly's PyroCMS pages module. Editors can drop the block onto any page to surface the latest blog entries, with configurable post limits, category filtering, and layout options, no template changes needed.

View on GitHub
Deep Wiki
Context7

Getting Started

This is not a standalone Laravel package — it is an addon for the PyroCMS 3 / Streams Platform. You need both anomaly/streams-platform (or the pyrocms/pyrocms distribution) and the Posts module installed before it does anything. Without the Posts module the block will register fine but render empty (see Gotchas).

Minimal install:

composer require anomaly/posts-module anomaly/recent_posts_block-extension
php artisan addon:install anomaly.module.posts
php artisan addon:publish anomaly.extension.recent_posts_block

If there is no stable tag on Packagist (very likely for an extension with zero dependents), fall back to the VCS repo:

"repositories": [{ "type": "vcs", "url": "https://github.com/anomalylabs/recent_posts_block-extension" }],
"require": { "anomaly/recent_posts_block-extension": "dev-master" },
"minimum-stability": "dev"

The addon's service provider is typically auto-discovered. If it isn't, add it to your application's provider list manually.

Where to look first — read three files in this order:

  1. src/RecentPostsBlockExtensionServiceProvider.php — this is where the block slug is bound (usually a protected $blocks = ['recent_posts' => RecentPostsBlock::class] array). The slug here is the key you use everywhere else.
  2. src/Block/RecentPostsBlock.php — the $view property, the field definitions that render the block's settings form in the control panel, and the data-accessor methods that hand posts to Twig.
  3. resources/views/content.twig — the actual markup; this is what you will almost always override.

First use case: open a page (Pages module) → Blocks tab → pick the recent-posts block → set the count → save → view the page. That single flow exercises registration, the settings form, the repository query, and the Twig rendering. Once that works, drop the same block into a theme template directly:

{{ block('recent_posts').render()|raw }}

Implementation Patterns

Register blocks under a project-owned slug rather than fighting the extension's slug. The vendor slug is tied to the addon namespace; if you need a second variant (e.g. "recent posts from a specific category"), register another entry pointing at your own subclass:

// app/Providers/AppServiceProvider.php or a dedicated BlocksServiceProvider
protected $blocks = [
    'recent_posts_featured' => \App\Blocks\FeaturedPostsBlock::class,
];
class FeaturedPostsBlock extends \Anomaly\RecentPostsBlockExtension\Block\RecentPostsBlock
{
    protected $view = 'theme::blocks.featured_posts';

    public function getPosts()
    {
        return parent::getPosts()->where('featured', true); // or use the PostCriteria
    }
}

Query through the repository contract, not Eloquent. The Posts module exposes Anomaly\PostsModule\Post\Contract\PostRepositoryInterface; using it keeps you inside the module's criteria system (published scope, ordering, eager loading) instead of reaching into a table that may be renamed or stream-prefixed:

public function getPosts()
{
    return app(PostRepositoryInterface::class)
        ->orderBy('publish_at', 'DESC')
        ->limit($this->fields->limit ?: 5)
        ->get();
}

Render in the theme either by slug or by iterating the returned collection. The block() helper returns the block instance, so any public accessor method is callable in Twig:

{% set recent = block('recent_posts') %}

<ul class="recent-posts">
    {% for post in recent.posts %}
        <li>
            <a href="{{ post.route('view') }}">{{ post.title }}</a>
            <time>{{ post.publish_at.format('M j, Y') }}</time>
        </li>
    {% endfor %}
</ul>

Cache at the block boundary, not the query. Block output is re-evaluated on every page render, so wrap it once:

public function render()
{
    return cache()->remember(
        'block.recent_posts.' . $this->entry->id,
        now()->addMinutes(15),
        fn () => parent::render()
    );
}

Bust that key from a Posts module observer (PostModel::saved/deleted) or accept the staleness and lower the TTL.

Wire it into the Pages module's section system if you're building a theme — assign page blocks to named sections in the admin and render sections in your layout, so editors can reorder blocks without touching Twig.

Gotchas and Tips

  • Maintenance status is the biggest risk. This sits in the archived PyroCMS 3 / Streams Platform ecosystem, which is effectively dormant. Expect PHP 7.4–8.0 era code, illuminate/* constraints that conflict with Laravel 9+, and no security patches. Treat it as a reference implementation to copy into your own addon rather than a long-lived dependency.
  • Silent empty output almost always means a missing dependency. If the block renders nothing, confirm anomaly/posts-module is installed and migrated, and that PostRepositoryInterface resolves (php artisan tinker → app(PostRepositoryInterface::class)). A container binding exception in a block often gets swallowed into an empty render.
  • Blocks are database records, not just classes. Changing a block's field definitions does not retroactively update existing block entries. After editing fields, re-open and re-save the block in the control panel, or you'll hit "undefined index" in Twig. Renaming a field slug silently orphans stored values.
  • Unpublished posts leak into listings if you query the repository without the published criteria. Check the accessor method's ordering and scope; add where('status', 'published') or the module's criteria explicitly rather than assuming the default is filtered.
  • N+1 on author/category/tags. The default query returns posts without relations; eager-load them (->with(['author', 'category'])) or you'll fire a query per post per block per page render.
  • |raw is required when echoing block(...).render() — Twig autoescaping will otherwise dump the HTML as text, which reads like "the block is broken" when it isn't.
  • View overrides: the namespace (anomaly.extension.recent_posts_block::content) is derived from the composer package name. The most reliable override is to subclass the block and point $view at your theme's namespace, as shown above. If you do override by mirroring the path, remember php artisan view:clear — cached compiled Twig is the usual culprit for "my changes did nothing."
  • Forking requires a global rename. If you copy this package, change the composer name, addon namespace, service-provider namespace, the Twig view namespace, and the block slug together. A partial rename produces a block that registers but points at a view that doesn't exist.
  • Debugging shortcuts: php artisan addon:list to confirm registration, php artisan addon:publish to re-publish views/assets, and php artisan route:list | grep recent to verify nothing collided. For query problems, DB::listen() around a single block render is faster than guessing.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator