softspring/customer-bundle
Softspring Customer Bundle is a PHP/Symfony package for modeling and managing customer accounts, profiles, addresses, and contact data. It offers reusable entities, services, and forms to speed up building customer management features in Symfony applications.
Before anything else, confirm the stack fit — it defines what "day-to-day" even means here:
composer require + bundle registration, and you get Doctrine entities, forms, and services you can use immediately.For a Symfony host, the minimal path:
composer require softspring/customer-bundle
If Symfony Flex has no recipe (likely, given zero adoption), register it manually in config/bundles.php:
return [
// ...
Softspring\CustomerBundle\SoftspringCustomerBundle::class => ['all' => true],
];
Then read the source in this order — it's the only documentation you have:
src/Entity/ — the customer, address, and contact models. This is the whole value of the bundle.src/Resources/config/ — service definitions and any bundle configuration (entity class parameters are common in these bundles).src/Form/ — the form types and their validation constraints.Migrations/ or mapping files (*.orm.xml, *.yaml, or PHP attributes) to see the exact schema the entities imply.First use case: in a Symfony app, wire a controller that uses the bundle's customer entity and create form to persist a customer with a primary address. That single flow (create customer → attach address → validate → persist) exercises the entity relations, the form, and the validation constraints — i.e. the entire useful surface of the bundle — and tells you within an hour whether the model matches your domain.
In Laravel, the equivalent "first use case" is writing one migration plus one Eloquent model for customers and addresses, copied from the bundle's field list and relations. Do that before deciding whether to port more.
Entity-first CRUD (Symfony). The bundle's centre of gravity is the data model. The pattern is: inject the entity manager, instantiate (or fetch) the customer entity, hand it to the bundle's form type, validate, persist. Don't fight the forms — if the bundle's form type doesn't cover a field you need, create a form type in your own app that extends or wraps it rather than editing vendor code.
Doctrine mapping overrides. These bundles almost always let you swap the entity class via configuration (a class parameter or a doctrine.orm.mapping override). If you need extra columns on a customer — a stripe_id, an external_ref, a GDPR deleted_at — prefer subclassing or mapping-extension over forking the bundle. Check src/Resources/config/ for whether the entity class is a parameter before you assume it's hard-coded.
Wrap it behind your own service. Create a CustomerManager (or repository) in your app that is the only thing calling the bundle's services. Controllers and other domain code talk to your wrapper. This is the single most valuable pattern here: with zero upstream maintenance, you want the blast radius of replacing or forking the bundle to be one class, not every controller.
Reuse the validation constraints, not just the entities. The form types carry the validation logic (required fields, address format, contact rules). When you build your own API or console command that creates customers outside the forms, apply the same constraints explicitly — otherwise you get customer records that violate the invariants the forms enforce, and the bundle won't protect you.
Porting pattern (Laravel). Treat the bundle's entity definitions as a spec, then rebuild in your own idioms:
ManyToOne customer→address becomes an Eloquent belongsTo / hasMany, plus a is_default boolean you manage with a scope.customers — do not blindly create a contacts table.FormRequest rules and/or model-level validation.Write a short spec first: field list, nullability, relations, and who owns default-address enforcement. That document is the real deliverable if you go the port route.
Typical workflow, end to end:
doctrine:schema:validate, generate a diff, create a customer with an address.composer.json, commit the lock, wrap behind your service, and record a compatibility matrix (PHP × Symfony major × bundle tag × DB driver).The stack mismatch is not a detail, it's the headline. Every "saving" from this bundle evaporates if you're in Laravel: Eloquent and Doctrine do not interoperate, and you will spend more time bridging than writing the two models yourself. Decide this before you write any integration code.
Bundle-shipped migrations vs yours. If the bundle ships migrations or a full schema, do not generate competing first-party migrations for the same tables. Pick one owner. Mixing them produces schema drift that is painful to untangle later, especially once customer rows exist.
Entity class hard-coding is the common trap. Confirm whether the customer/address classes are configurable. If they aren't, your "extend the entity" plan becomes "fork the bundle" — worth knowing on day one, not week three.
Debugging the container. Most failures on a dormant Symfony bundle are container-compilation errors, not runtime errors. When something breaks after a dependency bump: bin/console cache:clear, then bin/console debug:container | grep -i customer, then composer why-not softspring/customer-bundle to see the actual constraint conflict. Don't debug the code until you've ruled out resolution and cache.
Check the declared support matrix yourself. Look at composer.json for php and symfony/* constraints before assuming it works on your Symfony 6/7 app. An unmaintained bundle pinned to an older major is a silent blocker.
PII defaults you didn't author. This is customer data — names, addresses, contact details, potentially account state. Read the entity defaults and any controller/service logic that serialises customers (e.g. for admin screens) before pointing it at production data. Defaults around soft-deletes, logging of entities, and serialisation groups are where leaks hide.
Zero upstream maintenance is a design constraint. No stars, no dependents, no visible release cadence. Assume "fork on first bug." Budget an owner, a fork under your org, and a documented patch process. If you're not prepared to own this code, don't depend on it — copy the schema.
Extension points worth knowing, in order of preference: (1) override the entity class via config, (2) subclass the entity/form type in your app, (3) decorate the bundle's services, (4) fork. Editing files in vendor/ is never the answer — it's lost on the next composer update, and with no upstream, there is no "next update" to carry your patch forward.
Profile before launch. Customer-to-address relations are the classic N+1 in this shape of bundle. Fetch a page of customers with their addresses and count the queries. If the bundle doesn't eager-load by default, add fetch="EAGER" at your mapping layer or eager-load explicitly in your repository — not in the bundle.
How can I help you explore Laravel packages today?