madalynn/plum-bundle
Symfony bundle that integrates the Plum library into Symfony applications, registering its services in the container and exposing configuration for quick setup. Provides a convenient starting point for using Plum in Symfony projects, with autoconfiguration and sensible defaults to reduce boilerplate...
Read this first: the package is not installable in a Laravel app. madalynn/plum-bundle is a Symfony2 bundle — it ships a class extending Symfony\Component\HttpKernel\Bundle\Bundle, registers services into Symfony's DI container, and exposes commands through the Symfony Console. There is no service provider, no config publish tag, no Artisan command, no facade. composer require madalynn/plum-bundle will fail resolution on any Laravel 8+ project because of the symfony/framework-bundle ~2.x and PHP 5.x/early-7.x constraints. Do not spend time trying to force it.
So your "getting started" is really a triage step. Take thirty minutes and answer one question: is the requirement "use Plum" or "have a scripted, repeatable deploy"? In 99% of Laravel contexts it's the second, and the bundle is irrelevant to it. Confirm that before touching anything.
Where to look first, in order:
composer.json of the bundle — the require block tells you immediately whether this is Symfony 2.x pinned and whether the PHP floor conflicts with your runtime. This is the whole decision, compressed into a dozen lines.DependencyInjection/*Extension.php) and its Configuration class — this is the actual content of the package. It's small. Reading it takes ten minutes and tells you exactly which config keys exist and which Plum services get wired.madalynn/plum), not the bundle. Plum is the thing with the deploy logic; the bundle is glue. If you want Plum's release-directory model, you want the standalone CLI/PHAR.Your realistic first use case is not "install the bundle," it's: take Plum's release layout (releases/, shared/, current symlink, rollback to previous release) and express it as a Laravel-side deploy. Concretely — a single-server staging deploy where shared/ holds .env, storage/, and storage/app/public/uploads; current is symlinked to the newest release directory; the last five releases are retained; and php artisan migrate --force runs as a post-deploy step. That's the entire shape, and you can build it with a Deployer recipe or a shell script in a day.
If you genuinely inherited a Symfony2 app that already uses this bundle, then the steps are: pin the exact version, register new Madalynn\PlumBundle\MadalynnPlumBundle() in app/AppKernel.php, add the plum: block to app/config/config_staging.yml, point it at a recipe file, and run setup → deploy → rollback against staging before touching production. Don't model production until staging is boring.
Recipe-as-code, config-as-environment-switch. Plum's core abstraction is a recipe file that declares servers, roles, deploy path, shared files/dirs, symlink targets, and callbacks. The pattern that ages well is: one recipe per environment, kept in version control, diffed in review, and selected via Symfony config (config_staging.yml vs config_prod.yml) — never via if branches inside a single recipe. Hostnames travel as Symfony parameters (%deploy_host%) resolved before Plum runs, so the recipe stays environment-agnostic and secrets stay out of it.
# Illustrative — verify key names against the pinned version's Configuration class
plum:
recipe: "%kernel.root_dir%/config/plum/staging.php"
Hooks are where the actual deploy lives. The bundle just wires services; the value is in what your recipe's callbacks do. The pattern worth carrying to Laravel:
current.migrate --force, clear and warm caches in the new release path, flip the current symlink, reload php-fpm or queue workers.current at the previous release and re-run the cache warm — the symlink flip alone leaves stale compiled config in older Laravel/Symfony versions.Thin console command, thick CI job. Treat the framework command as an entry point only; CI invokes it. In a Laravel port the equivalent is a small Artisan command (deploy:staging) that shells out to your deploy tool, called from a gated GitHub Actions/GitLab CI job. Keeping the command thin means the deploy works identically from CI and from a break-glass terminal session, and you never end up with logic that only exists inside a Symfony console command you can't run anywhere else.
Wrapping a third-party CLI is the portable pattern. If you like the bundle's approach, replicate it in Laravel with a service provider binding a Deployer/PlumRunner service and two commands. That's roughly forty lines and it's yours to maintain — which is the same ownership burden you'd take on by adopting a zero-dependent package, minus the Symfony2 constraint.
Gated invocation. The most important integration tip in this whole area: deployment must be a CI gate, not a laptop command. Whatever you build, put an environment guard (branch check, manual approval, --force flag required for production) so a developer cannot accidentally run a production deploy from php artisan on a local machine.
Composer will fight you, loudly. Any attempt to install this into a modern app results in a resolution conflict — Symfony 2.x components pin old PHP, and current Laravel requires PHP 8.1+. If you somehow force it with --ignore-platform-reqs, you get two framework runtimes in one autoloader and a class-name collision on things like Container and Kernel. Don't. There is no supported path.
Config key drift is real. The package predates stable Symfony config conventions, and key names differ across tags (the project is effectively unmaintained with 26 stars and zero dependents, so nothing pins the docs to the code). Always read the Configuration class for your pinned version rather than trusting the README. Budget time for this if you inherit a Symfony2 app using it.
Symlink flips are not atomic across filesystems. Plum's release model assumes releases/ and current live on the same filesystem; putting shared/ on a separate mount from releases/ produces deploys that succeed and then serve 404s. Check mounts before you design the layout.
Shared directories are a footgun for storage. Anything under shared/ that Laravel/Symfony writes to (logs, uploads, cache, sessions) must be present before the release is created, and permissions must survive the copy. The classic failure is storage/framework/views created as root during a hook and then unwritable by php-fpm.
Cache clearing order matters. Clear/warm cache against the new release directory before the symlink flip, not after — otherwise a window exists where current points at unreleased code with stale compiled config, and requests 500. Same principle applies to config:cache in Laravel.
Partial deploys have no rollback beyond the previous release. Plum gives you release-directory rollback; it does not give you database rollback, asset-version rollback, or migration down-paths. Migrations must be written forward-compatible (expand/contract) or rollback is a lie. This is the single most expensive gotcha to discover in an incident.
Debugging a failed deploy: the recipe's hook output is your only real signal — capture it. Run the deploy against staging with verbose output, confirm each hook's exit code is checked (a silently-ignored non-zero exit from migrate is how you get a half-deployed app that still serves traffic), and verify the symlink target and releases/ listing after the run before declaring success.
Extension points, if you inherit it. The useful surfaces are the DI extension (add your own service definitions alongside), the recipe callbacks (put real work there), and the console command registration (add your own commands in the same namespace). Do not extend the bundle class itself — it's an empty Symfony bundle shim and subclassing buys you nothing.
Strategic tip, said plainly: low install effort, low risk in isolation, high strategic risk. Adopt only as a maintenance-mode stopgap with a named owner and a documented exit path off Symfony2. For anything on the forward roadmap — greenfield, Symfony 4+, Laravel, containers, or multi-environment portfolios — route the effort to Deployer, Envoy, or CI-native pipelines instead, and use this package's small DI/command structure purely as a reference pattern for wrapping your chosen tool.
How can I help you explore Laravel packages today?