mpp/message-bundle
mpp/message-bundle is a Symfony bundle for adding private messaging to PHP applications, providing message entities, conversation threading, and services for sending, storing, and retrieving user-to-user messages in inbox-style conversations.
This is a Symfony bundle, not a Laravel package — that distinction drives everything below. It ships a Bundle class and a DependencyInjection extension, so there is no service provider, no config/mpp-message.php, and no facade. In a Laravel app you would be wiring it manually or running it inside a Symfony kernel; if the project is Laravel-first, treat this as a "borrow the domain logic, skip the bundle" situation and pull the underlying services in as plain classes.
Read the version number before anything else. The latest release is v4.0.0, and its entire changelog is "compatibility with symfony 7". That is a major bump for a compatibility chore, which means the previous major (3.x) is the line for anything below Symfony 7. Check composer.json's require block for the actual constraint — if it reads ^7.0 with no lower bound, you cannot install this on Symfony 6.4 LTS, and you should stay on 3.x there.
Minimal path in a Symfony app:
composer require mpp/message-bundle — pin explicitly. Decide up front: "^4.0" if you are on Symfony 7.x, "^3.0" if you are on 6.4 LTS. Do not use *.config/bundles.php:
return [
// ...
Mpp\MessageBundle\MppMessageBundle::class => ['all' => true],
];
composer.json and its src/ for changed method signatures. Symfony 7 removed a batch of deprecated patterns (return-type declarations on Extension/Bundle methods, @required on setters, untyped ContainerBuilder params). A bundle that "only" bumped compatibility usually had to touch signatures to get there — verify each class you subclass or decorate.src/DependencyInjection/Configuration.php — the authoritative list of config keys. The README is the marketing version; the Configuration tree is the truth.src/DependencyInjection/*Extension.php — shows which services are registered, their IDs, and any aliases you can autowire.config/packages/mpp_message.yaml) and only set what Configuration.php declares. Anything else will fail bundle boot with "Unrecognized option".bin/console cache:clear. Bundle config is compiled; stale cache is the #1 reason "my config change did nothing."First use case to prove the wiring: send one message to one recipient and dump the result. Inject the delivery service (check the extension for its ID/alias, likely something like Mpp\MessageBundle\Service\... or a MessageDispatcher-style interface), construct your payload, call the send method, and dump() the return value and any thrown exception. Do this before touching templates, queues, or persistence — it isolates "is the bundle wired" from "is my message correct."
Constructor injection over container pulls. The extension registers services with autowiring/autoconfiguration in the typical Symfony style. Type-hint the interfaces the bundle exposes and let the container resolve them; avoid $container->get('mpp_message.something') because those IDs are not a stable API and will change between minor versions of a 0-star package. Service IDs are also the likeliest thing to move in a major bump like 4.0, so container pulls are the code most likely to break on upgrade.
Channel/handler decoration. A "message delivery" bundle almost always exposes a delivery contract (an interface with a send()/deliver() method) plus one or more concrete transports. The clean integration point is Symfony's decorator pattern: implement the same interface, wrap the bundle's service, and add logging, retry, or a fallback channel without forking the bundle.
final class LoggingDelivery implements DeliveryInterface
{
public function __construct(
private DeliveryInterface $inner,
private LoggerInterface $logger,
) {}
public function send(MessageInterface $message): DeliveryResult
{
$this->logger->info('delivering', ['id' => $message->getId()]);
return $this->inner->send($message);
}
}
Register it in services.yaml with decorates: <bundle service id> and arguments: ['@.inner']. This is the single most valuable pattern for an unmaintained package: you keep the bundle's plumbing and own the behaviour. When you go 3.x → 4.x, this decorator is also your upgrade alarm — if the interface signature changed, your class fails to compile at container build time rather than silently misbehaving in production.
Domain listener dispatch. If the bundle fires events after delivery, prefer listening to those over polling your own tables:
#[AsEventListener(event: MessageDeliveredEvent::class)]
public function onDelivered(MessageDeliveredEvent $event): void { /* ... */ }
Confirm the actual event class names from src/Event/ before writing the listener — do not assume them, and re-confirm on the 4.x jump.
Queue/async offload. Symmetric with Laravel's queue, Symfony Messenger is the usual companion. If the bundle's delivery is synchronous and slow (SMTP, HTTP), wrap the send in a Messenger message and handle it in a worker:
$this->bus->dispatch(new SendMppMessage($messageId));
Then bin/console messenger:consume async in a supervisor-managed process. This keeps the controller fast regardless of what the bundle does internally. Note that Symfony 7 tightened Messenger's typed-message handling and HandlerInterface generics — if the bundle ships its own handlers, test the worker path on 4.0, not just the sync path.
Testing pattern. Because this is a third-party bundle with near-zero adoption, never hit its real transport in tests. Bind your own in-memory implementation of the delivery interface in config/services_test.yaml, or use symfony/phpunit-bridge's KernelTestCase and assert on the fake. This also gives you a regression harness: if a bundle update changes a signature, your fake fails loudly. This is especially relevant now — a compatibility-only major bump is exactly the kind of release that quietly changes a return type, and a fake implementing the interface catches it in CI before deploy.
symfony/console and symfony/http-foundation under the hood is unrelated to booting MppMessageBundle. The "borrow the domain logic" advice from Getting Started stands.src/ directly; it is likely small enough to do in an hour, and that hour is mandatory before you commit to the package."^4.0" or a dev-lock) in composer.json and add composer.lock to VCS. Have an exit plan: because the surface is likely small, a fork-and-adopt is cheap — the decorator pattern above makes that swap nearly trivial.Configuration.php tree doesn't actually define. Diff your YAML against the tree. This gets more likely after a major bump — READMEs lag Configuration trees.APP_ENV-specific configs and compiled containers will mask your edits; cache:clear before every debugging session on config issues. After a major upgrade, rm -rf var/cache outright rather than trusting cache:clear.bundles.php.Migrations/ or Entity/ directory. If the bundle owns tables, you need doctrine/doctrine-migrations-bundle wired to the bundle's migration path, and you must verify the schema is created before the first send or you'll get a confusing "table not found" at runtime rather than at boot. Also verify the Doctrine ORM constraint survived the 4.0 bump — Symfony 7 compatibility often forces a Doctrine ORM 3 requirement, which changes proxy and attribute behaviour.services.yaml, (4) fork. Avoid (3) where possible — it breaks on upgrades and is invisible to the next developer. With 4.0 out, any (3) you had against 3.x is now a compile-time error waiting to happen.How can I help you explore Laravel packages today?