Installation
composer require bloghoven/blog-bundle
Add the bundle to config/bundles.php (Symfony) or config/app.php (Laravel via Symfony bridge):
Bloghoven\BlogBundle\BloghovenBlogBundle::class => ['all' => true],
Publish Assets & Config
php artisan vendor:publish --provider="Bloghoven\BlogBundle\BloghovenBlogBundle" --tag="config"
php artisan vendor:publish --provider="Bloghoven\BlogBundle\BloghovenBlogBundle" --tag="public"
This generates:
config/bloghoven.php (default config)public/vendor/bloghoven/First Use Case: Displaying a Blog Post
Inject the BlogPostRepository into a controller:
use Bloghoven\BlogBundle\Repository\BlogPostRepository;
public function showPost(BlogPostRepository $posts)
{
$post = $posts->find(1); // Fetch by ID
return view('blog.post', compact('post'));
}
Create a Blade template (resources/views/blog/post.blade.php):
<h1>{{ $post->title }}</h1>
<div>{!! $post->content !!}</div> <!-- Supports HTML -->
CRUD Operations Use the repository pattern for all blog operations:
// Create
$post = $posts->create([
'title' => 'Laravel Tips',
'slug' => 'laravel-tips',
'content' => '<p>Tip 1: Use...</p>',
'author_id' => auth()->id(),
]);
// Update
$posts->update($post->id, ['title' => 'Updated Title']);
// Delete
$posts->delete($post->id);
Querying Posts Filter posts via the repository:
// By category
$posts = $posts->findBy(['category' => 'laravel']);
// Published only
$posts = $posts->findBy(['published_at' => null]); // Non-null = published
// Paginated list
$posts = $posts->findAll(['limit' => 10, 'offset' => 0]);
Taxonomy Management
Use BlogCategoryRepository and BlogTagRepository:
$categories = $categories->findAll();
$tags = $tags->findBy(['post_id' => $post->id]);
Asset Handling
Upload images via the BlogPostImageUploader service:
$uploader = app('bloghoven.blog_post_image_uploader');
$path = $uploader->upload($request->file('image'));
Bloghoven\BlogBundle\Form\Type\BlogPostType for custom validation.blog.post.created or blog.post.updated for post-processing (e.g., notifications).BlogPostResource (if provided) for API responses:
return new BlogPostResource($post);
Slug Conflicts
slug field in create()/update() to avoid collisions.if ($posts->exists(['slug' => $slug])) {
$slug = Str::slug($title . ' ' . time());
}
Content Sanitization
content field accepts raw HTML. Sanitize user input to prevent XSS:
use Symfony\Component\DomCrawler\Crawler;
$cleanContent = (new Crawler($post->content))->html();
Database Migrations
meta_title) require manual migration:
php artisan make:migration add_meta_to_posts --table=blog_posts
Caching Quirks
'cache' => [
'enabled' => true,
'ttl' => 3600, // 1 hour
],
\DB::enableQueryLog();
$posts->find(1);
dd(\DB::getQueryLog());
EventSubscriber:
public function onPostCreated(PostCreatedEvent $event)
{
\Log::debug('Post created:', ['post' => $event->getPost()]);
}
Custom Fields
Extend the BlogPost entity or use a trait:
namespace App\Models;
use Bloghoven\BlogBundle\Entity\BlogPost as BasePost;
class BlogPost extends BasePost
{
protected $customField = 'value';
}
Override Templates
Copy vendor templates to your project (e.g., resources/views/vendor/bloghoven/) to customize:
post/show.html.twig (Symfony) or post.blade.php (Laravel).Add Validation Extend the form type:
namespace App\Form\Type;
use Bloghoven\BlogBundle\Form\Type\BlogPostType as BaseType;
class BlogPostType extends BaseType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('custom_field', TextType::class);
}
}
API Extensions Create a custom resource:
namespace App\Http\Resources;
use Bloghoven\BlogBundle\Http\Resources\BlogPostResource as BaseResource;
class CustomPostResource extends BaseResource
{
public function toArray($request)
{
$array = parent::toArray($request);
$array['custom_data'] = $this->customField;
return $array;
}
}
How can I help you explore Laravel packages today?