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.
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:
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.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.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 }}
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.
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.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.where('status', 'published') or the module's criteria explicitly rather than assuming the default is filtered.->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.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."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.How can I help you explore Laravel packages today?