Model And Entities
The main design decision in account-bundle is not technical. It is deciding that the account, not the user, is the owner of the business data.
That is usually the right model when:
- one company or team owns the subscription
- several users work on the same data
- one user can belong to several organizations
- access must survive user changes
Core Shared Model
The bundle keeps the minimum common model small:
AccountInterface- exposes the account identity and name
Account- mapped superclass with the
namefield
- mapped superclass with the
AccountUserRelationInterface- represents a link between an account and a user
AccountUserRelation- mapped superclass for that relation
Your application owns the concrete Doctrine entities and extends these model classes.
Recommended Pattern For New Projects
For new code, the safest baseline is:
- let
Accountextend the bundled model - let
AccountimplementAccountManyUserRelationsInterface - let
UserimplementUserManyAccountRelationsInterface - create a concrete relation entity extending
AccountUserRelation - make account-owned business entities implement
AccountRelatedInterface - add
AccountFilterInterfacewhen those entities should be filtered automatically
This is a better long-term fit than the older SingleAccounted*, MultiAccounted*, and Complete* helpers, many of which are already deprecated.
Account Entity
A typical account entity extends the bundled model and adds ownership and memberships:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Softspring\AccountBundle\Entity\AccountManyUserRelationsTrait;
use Softspring\AccountBundle\Model\Account as AccountModel;
use Softspring\AccountBundle\Model\AccountManyUserRelationsInterface;
use Softspring\UserBundle\Entity\OwnerTrait;
use Softspring\UserBundle\Model\OwnerInterface;
#[ORM\Entity]
class Account extends AccountModel implements AccountManyUserRelationsInterface, OwnerInterface
{
use OwnerTrait;
use AccountManyUserRelationsTrait;
public function __construct()
{
$this->userRelations = new ArrayCollection();
}
}
If you want to reuse the bundled admin forms and templates, keeping an owner field is the safest choice.
User Entity
If users can belong to several accounts, the user usually exposes account relations:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Softspring\AccountBundle\Entity\UserManyAccountRelationsTrait;
use Softspring\AccountBundle\Model\UserManyAccountRelationsInterface;
use Softspring\UserBundle\Model\User as UserModel;
#[ORM\Entity]
class User extends UserModel implements UserManyAccountRelationsInterface
{
use UserManyAccountRelationsTrait;
public function __construct()
{
parent::__construct();
$this->accountRelations = new ArrayCollection();
}
}
Membership Relation Entity
The account-user relation is usually where real product rules live. It is a good place for:
- account-specific roles such as owner, admin, manager, or member
- who granted access
- invitation metadata
- timestamps or status
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Softspring\AccountBundle\Model\AccountUserRelation as AccountUserRelationModel;
use Softspring\AccountBundle\Model\AccountUserRelationInterface;
use Softspring\UserBundle\Model\UserInterface;
#[ORM\Entity]
class AccountUserRelation extends AccountUserRelationModel implements AccountUserRelationInterface
{
#[ORM\ManyToOne(targetEntity: UserInterface::class)]
protected ?UserInterface $grantedBy = null;
#[ORM\Column(type: 'json')]
protected array $roles = [];
}
This is usually the right place to model who can manage billing, users, projects, or other account-specific actions.
Account-Owned Business Entities
Any entity that belongs to one account should expose that relation explicitly:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Softspring\AccountBundle\Entity\AccountTrait;
use Softspring\AccountBundle\Model\AccountFilterInterface;
use Softspring\AccountBundle\Model\AccountRelatedInterface;
#[ORM\Entity]
class Project implements AccountRelatedInterface, AccountFilterInterface
{
use AccountTrait;
}
Use AccountRelatedInterface when the entity belongs to one account. Add AccountFilterInterface when reads should be scoped automatically on account-aware routes.
Choosing Between Ownership Styles
Use the bundle when the real business owner is the account:
- subscriptions belong to the company account
- projects belong to the provider account
- invoices belong to the customer account
- several people act on behalf of that account
Do not force the bundle if your application is fundamentally single-user and ownership never needs to move above the user.