lexik/mailer-bundle
Symfony bundle for managing transactional email templates stored in a database. Define layouts, edit content from an admin interface, use placeholders, and send messages through a simple mailer service, keeping email text out of your code.
The package models "entries" that carry a categorising field. As of v2.0.11 that field is called entry_type everywhere — column, attribute, validation key, and serialised output. Anything you wrote against v2.0.10 or earlier that says type will silently stop working, so treat this release as a major even though the version number says patch.
Minimal setup:
composer require <vendor>/<package>:^2.0.11
php artisan vendor:publish --provider="<Vendor>\<Package>\<PackageServiceProvider>"
php artisan migrate
Do the publish before migrating — the published migration stub is what creates the entry_type column, and you want the v2.0.11 stub, not a cached older one.
Where to look first, in order:
config/<package>.php — entry type definitions, defaults, and any type keys that need renaming.entry_type.Entry model (or whatever the package names it) — $fillable, casts, and scopes.First use case — creating and querying an entry:
use Vendor\Package\Models\Entry;
$entry = Entry::create([
'title' => 'First entry',
'entry_type' => 'article',
'body' => '...',
]);
Entry::where('entry_type', 'article')->latest()->paginate();
If that round-trips, the rename is wired up correctly in your app.
Always go through entry_type in the four places that matter. In practice a rename like this leaks into:
create() payloads.'entry_type' => ['required', Rule::in(...)]).allowedFilters(['entry_type']), and with where('type', ...) scattered in repositories.toArray() overrides — if you were relying on the package's default serialisation, the JSON key has changed to entry_type and any consumer (SPA, mobile app, webhook receiver) needs updating in lockstep.Centralise the type values. Rather than sprinkling string literals, define them once:
enum EntryType: string {
case Article = 'article';
case Note = 'note';
}
Then use EntryType::Article->value in seeders, factories, and validation Rule::enum(EntryType::class). When the package renames the field again (and given the versioning discipline here, assume it might), you only touch the boundary layer.
Keep a thin application-level wrapper. A repository or scope on your own model subclass localises the field name:
public function scopeOfType(Builder $q, EntryType $type): Builder
{
return $q->where('entry_type', $type->value);
}
Call sites then read ->ofType(EntryType::Note) and never mention the column. This is the cheapest insurance against the next rename.
Factories and seeders need the new key. The package's own factory should be updated, but any factory you published or overrode will still emit 'type' => ... and quietly drop it if type is no longer fillable. Check database/factories and any EntryFactory overrides.
Migration strategy for existing data. The release notes say "use entry_type instead of type" but don't state whether the package ships a rename migration or just changes the stub. Two cases:
php artisan migrate, then verify with Schema::hasColumn('entries', 'entry_type').Schema::table('entries', function (Blueprint $table) {
$table->renameColumn('type', 'entry_type');
});
renameColumn needs doctrine/dbal on older Laravel versions; on Laravel 10+ it's native. Test on a copy of production data before running for real, and remember SQLite in your test suite handles renames differently from MySQL.
Update tests as assertions, not just code. assertDatabaseHas('entries', ['entry_type' => 'article']) is the assertion that catches a partial migration. Grep your test suite for 'type' too — a passing suite that still asserts on type means the assertion is no longer checking anything meaningful.
The version number is misleading. Renaming a public attribute in a patch release (v2.0.11) violates semver. Pin the package ("<vendor>/<package>": "2.0.10" as a stopgap if you can't migrate immediately), and don't let composer update run unattended on this dependency until you've migrated. Consider a composer.lock review step in CI.
Silent failure is the primary risk. If type is no longer in $fillable, Entry::create(['type' => 'article']) doesn't throw — it just drops the value, leaving entry_type null. Your writes appear to succeed and your reads return nothing. Guard against this by making the column NOT NULL where the domain allows, and by validating entry_type as required at the boundary.
Grep comprehensively, not just for ->type. The rename shows up as: ->type, 'type', "type", ['type'], ->whereType(, orderBy('type'), sortable('type'), $request->type, type: in named arguments, Blade $entry->type, JSON fixtures, and OpenAPI/Postman collections. Do a repo-wide search including front-end assets and docs.
Config cache will bite you. If the config file has a type key that you renamed to entry_type, a stale bootstrap/cache/config.php can keep the old value alive. Run php artisan config:clear (and optimize:clear in CI) after upgrading.
Watch accessors, casts, and appended attributes. Any getTypeAttribute(), $casts = ['type' => ...], or $appends = ['type'] in your own model subclass silently shadows or duplicates the package behaviour. If the package adds an entry_type accessor and you still append type, you'll produce two keys in your JSON with divergent values.
Extension points to prefer over patching. If the package supports registering custom entry types via config, do it there rather than forking the model. If it doesn't, subclass the model and point the package's models.entry config key at your subclass — that survives future releases, hard-coded forks don't.
Debugging checklist when entries come back empty: (1) Schema::hasColumn('entries', 'entry_type'); (2) $entry->getFillable(); (3) dd(Entry::first()?->getAttributes()) to see the real column names; (4) config('database.connections') sanity check you're on the DB you think you are. In that order — it's almost always the first or second.
Add a regression test for the public contract. One test asserting the serialised shape (assertJsonStructure(['data' => [['entry_type', ...]]])) and one asserting a create-then-read round trip. That pair catches this class of breakage in future releases before it reaches staging.
How can I help you explore Laravel packages today?