jeroendesloovere/vcard-bundle
Symfony bundle for generating and downloading vCard (VCF) contact files from PHP objects, wrapping the jeroendesloovere/vcard library with service integration and helpers for Symfony applications.
Architecture fit — Poor for a Laravel codebase, by definition. This is a Symfony2 bundle, not a Laravel package: it ships a *Bundle class extending Symfony\Component\HttpKernel\Bundle\Bundle, XML service definitions, and a DependencyInjection extension. Laravel has no bundle auto-discovery, no AppKernel registration, and no Symfony container compiler pass to hook into. Nothing in the package targets Laravel's service container, facades, config publishing, or package discovery. The only salvageable asset is the framework-agnostic vCard generator it wraps (jeroendesloovere/vcard), which is plain PHP and can be consumed from Laravel — but at that point we are not adopting this package, we are adopting its dependency.
Integration feasibility — Low as-is, moderate via extraction. The composer.json was last touched in 2018 and constrains symfony/framework-bundle to 2.x/3.x. A modern Laravel app (10/11/12) resolves Symfony components at ^6/^7. Those constraints collide, so composer require will either fail outright or force a downgrade of shared Symfony packages that Laravel itself depends on. That is a hard blocker, not a nuisance. Feasibility only becomes real if we target the underlying library directly, or replace it.
Technical risk — High and mostly unbounded.
\n sequences) in user-supplied fields such as ORG, TITLE, or NOTE allow property injection into the generated card. In any flow where we generate cards from user profile data, this needs explicit sanitization we would have to add ourselves.Key questions
.vcf files? Parsing changes the build-vs-buy calculus entirely and pushes toward sabre/vobject.sabre/vobject, which is maintained, widely deployed, and handles both generation and parsing?Verdict: do not adopt the bundle. Treat it as a reference implementation for the underlying generator, and either pull jeroendesloovere/vcard directly behind our own interface or build a thin RFC 6350 writer with sabre/vobject as the parser if inbound handling is in scope.
Stack fit — The bundle layer is a non-starter; the value is in the generator. In a Laravel app, the correct shape is a first-party VCardService (or VCardBuilder value object) registered as a singleton, injected into the export controller or queued job, and exposed through an optional facade if the team prefers. Configuration — default version, charset, organization defaults — belongs in config/vcard.php and is published via a service provider we write. This is roughly a half-day of scaffolding and gives us a seam to swap the generator later without touching call sites.
Migration path — Two viable routes, in order of preference:
jeroendesloovere/vcard (the non-Symfony sibling package). Lowest delta from the original intent, plain PHP, no Symfony kernel coupling. We pin it, wrap it, and accept that we own the risk surface ourselves.sabre/vobject. Actively maintained, used across calendar/contact infrastructure, handles generation and parsing, and correctly implements line folding and escaping. Cost is a small rewrite of the builder; benefit is a supported dependency and no fork obligation.If either route stalls on a deadline, the fallback is a ~200–300 line in-house writer targeting vCard 3.0 with explicit escaping and folding — genuinely tractable, and it removes the dependency question permanently.
Compatibility — Verify PHP 8.2/8.3 behavior before committing to option 1: the 2018 code predates typed properties and PHP 8 deprecations, and it will likely emit deprecation notices and may fail on stricter string/array handling. Also confirm UTF-8 handling end-to-end rather than trusting the default charset. For option 2, check the sabre/vobject major version against our PHP floor.
Sequencing
VCardGenerator contract with a single generate(ContactData): string method, plus an escaping/sanitization policy for user-supplied fields.Maintenance — Effectively 100% ours. The upstream is dormant, so any bug fix, PHP compatibility patch, or spec update is a fork we maintain. Budget a few hours per PHP minor upgrade and per dependency-security sweep. Option 2 (sabre/vobject) shifts most of this back to a vendor; that is the primary operational argument for replacing rather than wrapping.
Support — No commercial support, no active maintainer response, no security disclosure channel. Escalation path is "read the source." For a contact-export feature this is tolerable; for anything customer-facing at scale it will be raised in architecture review. Note also that 0 dependents means no community knowledge base — no Stack Overflow answers, no blog posts, no prior art when output looks wrong on a specific device.
Scaling — Generation itself is trivially cheap: string assembly, no I/O, no external calls. A single card is well under a millisecond; tens of thousands in a batch export are CPU-bound but parallelizable across queue workers. The real scaling concern is memory when embedding photos in base64 into bulk archives — stream per-contact rather than building an in-memory collection. The bundle's Symfony DI bootstrapping is irrelevant in Laravel since we will not use that layer at all.
Failure modes
Ramp-up — Low for the mechanics, moderate for the format. Any mid-level PHP engineer can generate a card in an afternoon. Getting it right — folding at 75 octets, escaping backslashes/commas/semicolons/newlines, choosing 3.0 vs 4.0 per target client, verifying against real devices — takes a day or two of spec reading and empirical testing. That knowledge is concentrated in whoever runs the spike, so pair on it and capture the device-matrix results in the ADR. Given the low opportunity score (14.6), treat this as commodity plumbing: buy or build the smallest thing that passes the device matrix, keep it behind an interface, and do not invest in the ecosystem further.
How can I help you explore Laravel packages today?