Reputation: 369
I have the main entity
/**
* @ORM\Entity()
*/
class Document
{
/**
* @var int
* @ORM\Id()
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var DocumentStatus
* @ORM\ManyToOne(targetEntity="DocumentStatus")
*/
private $status;
/**
* @var string
* @ORM\Column(type="text")
*/
private $text;
}
and the lookup "enum" entity (seeding on application deploy)
/**
* @ORM\Entity(repositoryClass="DocumentStatusRepository");
*/
class DocumentStatus
{
const DRAFT = 'draft';
const PENDING = 'pending';
const APPROVED = 'approved';
const DECLINED = 'declined';
/**
* @var int Surrogate primary key
* @ORM\Id()
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string Natural primary key (name for developers)
* @ORM\Column(type="string", unique=true)
*/
private $key;
/**
* @var string Short name for users
* @ORM\Column(type="string", unique=true)
*/
private $name;
/**
* @var string Full decription for users
* @ORM\Column(type="string", nullable=true, unique=true)
*/
private $description;
}
with simple repository
class DocumentStatusRepository extends EntityRepository
{
public function findOneByKey($key)
{
return parent::findOneBy(['key' => $key]);
}
}
I want to encapsulate domain logic of document lifecycle by intoducing methods like
public function __construct($text)
{
$this->text = $text;
$this->status = $something->getByKey(DocumentStatus::DRAFT);
}
public function approve()
{
try {
$this->doSomeDomainActions();
$this->status = $something->getByKey(DocumentSatus::DRAFT);
} catch (SomeDomainException($e)) {
throw new DocumentApproveException($e);
}
}
...
or
public function __construct($text)
{
$this->text = $text;
$this->status = $something->getDraftDocumentStatus()
}
public function approve()
{
$this->status = $something->getApprovedDocumentStatus()
}
...
without public setters. Also I want keep Document loose coupling and testable.
I see the next ways:
Are there other ways? Which way is easier and more convenient to use in the long term?
Upvotes: 1
Views: 164
Reputation: 1258
Using generated Identities for Document.
Now you generate the identity on the side of the database. So you save
Document from the domain perspective in inconsistent state. Entity/Aggregate should be identified
, if it has no id it shouldn't exists.
If you really want to keep to database serials, add method to the repository which will generate id for you.
Better way is to use uuid generator for example ramsey/uuid.
And inject
the id to the constructor
.
DocumentStatus
as Value Object
Why Document Status is Entity? It does look like a simple Value Object.
Then you can use Embeddable annotation. So it will leave within same table in the database, no need for doing inner joins.
DocumentStatus gets behaviour for example ->draftDocumentStatus(), which returns NEW DocumentStatus
with draft status, so you can switch the old instance one with new one. ORM will do the rest.
DocumentStatusRepository
If you really want to keep DocumentStatus as entity, which in my opinion is wrong
you shouldn't have DocumentStatusRepository.
Document is your aggregate root and the only entrance
to the DocumentStatus, should be by aggregate root.
So you will have DocumentRepository only, which will be responsible for rebuilding
the whole aggregate and saving
it.
Also you should change the mapping.
It should have FETCH=EAGER
type, so it will retrieve DocumentStatus with Document together.
Secondly you should do mapping with CASCADE=ALL
and ORPHANREMOVAL=TRUE
.
Otherwise, if you remove
Document, DocumentStatus will stay in the database
.
Upvotes: 1