0
votes

I am looking for advice on what the best practices for scenario below might be.

Scenario

I have some entities that use what I call "data as part of the schema": their data is part of the software and are not to be created/edited/deleted by the software during normal operation.

I'm using these to provide fixed lists of choices for other entities without using enum fields - because those are really part of the column definition and if I need to alter the column in any other way than to append a new value it results in a full table copy.

Such an entity is defined like this - readOnly=true parameter, manually assigned ID, unique slug, unique user-visible name, and private constructor. This isn't a good candidate for fixtures because it's not test data (talking about how Doctrine Fixtures wants to delete my "data as schema" is for another SO question)

/**
 * @ORM\Entity(readOnly=true)
 */
class SubmissionStatus
{
    /**
     * @var int
     * @ORM\Column(type="integer")
     * @ORM\Id()
     */
    protected $id;

    /**
     * @var string
     * @ORM\Column(type="string", length=191, unique=true)
     */
    protected $slug;

    /**
     * @var string
     * @ORM\Column(type="string", length=191, unique=true)
     */
    protected $name;

    private __construct() {}

    // ... other fields
}

I manage the values using Doctrine Migrations:

final class Version20181023132831 extends AbstractMigration
{
    public function up(Schema $schema) : void
    {
        $statuses = [
            ["id" => 1, "name" => "Pending", "slug" => "pending"],
            ["id" => 2, "name" => "Reported", "slug" => "reported"],
            ["id" => 3, "name" => "Rejected", "slug" => "rejected"],
            ["id" => 4, "name" => "Accepted", "slug" => "accepted"],
        ];

        foreach ($statuses as $status) {
            $this->addSql("INSERT INTO submission_status (id, name, slug) VALUES (:id, :name, :slug WHERE id = :id", [
                "id" => $status['id'],
                "name" => $status['name'],
                "slug" => $status['slug'],
            ]);
        }
    }
}

I do UPDATE and DELETE in future migrations if I need to update these values - also massaging existing Foreign Keys referring to these rows if necessary.

Other entities have @ORM\ManyToOne relationships to this entity.

The problem

What is the best way to refer to these in code?

Not all of these entities will be selected by users in ChoiceType fields in forms or come in through API calls. Some of them will be used just in code like $otherEntity->setStatus(...)

Current solution

Currently we've settled on doing this:

We're storing constants in the entity class for the IDs and slugs.

class SubmissionStatus
{
    const PENDING_ID = 1;
    const PENDING_SLUG = 'pending';
    const REPORTED_ID = 2;
    const REPORTED_SLUG = 'reported';
    const REJECTED_ID = 3;
    const REJECTED_SLUG = 'rejected';
    const ACCEPTED_ID = 4;
    const ACCEPTED_SLUG = 'accepted';
}

When we need to setStatus, we make database lookups:

$status = $em->getRepository(SubmissionStatus::class)->findOneBy(['slug' => SubmissionStatus::ACCEPTED_SLUG])
$otherEntity->setStatus($status);

Question

Is there are better way to do this?

I'm not a big fan of writing all that getRepository->findOneBy boilerplate all the time, especially if I need to reference several of this kind of entities. I would like something short, easy and quick to write when doing it repeatedly.

I would also prefer not to have to change too many places when I make changes to the values. One in the migration and one in the class constants is quite enough.

What I've thought of so far

I can't add a helper function to the entity itself (e.g. setStatusAccepted() or setStatus(SubmissionStatus::ACCEPTED_SLUG)) because I can't look up the referred entity from there.

An injected service which does a lookup on creation and stores references. Its usage would be e.g. $entity->setStatus($statusRef->accepted), $entity->setStatus($statusRef->get(SubmissionStatus::ACCEPTED_SLUG)). The first option breaks IDE code completion. The second one is a bit too long, even though shorter than $em->getRepository->find

1

1 Answers

0
votes

There is a property annotation that will help keep your IDE happy.

/**
 * One entry for each status 
 * @property-read SubmissionStatus $accepted 
 */
class SubmissionStatusReference
{
    public function __get($slug) {
        switch($slug) {
            // blah blah blah
            case 'accepted': 
                return $this->entityManager->getReference(etc...

I think this is the cleanest way to actually answer your question.

But having said that, I eventually stopped doing this sort of thing and instead of making actual status entities I just use slugs. Initially I was concerned about being able to change values and what not but as a practical matter I hardly ever need to do so. I use view transformers to convert the slugs to what you call names. And no id at all.