Question

In my current project I'm refactoring the code to get a DBAL. I have a class Entity that is the base class for all classes that model a database table. So there are several classes that inherit from Entity like Document, Article and so on.

abstract class Entity {
    /** @var DatabaseRequest $dbReuest */
    protected $dbRequest;

    /** @var Login $login */
    protected $login;

    /* some methods like insert(), update(), JsonSerialize(), etc.

}

Since all these classes have the same constructor __construct( DatabaseRequest $dbRequest, Login $login ) and I don't want to throw around those two paramenters, I also made this:

class EntityFactory {

  public function __construct( DatabaseRequest $dbRequest, Login $login )
  {
      $this->dbRequest = $dbRequest;
      $this->login = $login;
  }

  public function makeEntity( string $class )
  {

    if ( $this->extendsEntity( $class ) ) {

        $reflection = new \ReflectionClass( $class );
        $construct = $reflection->getConstructor();
        $params = $construct->getParameters();


        return new $class( clone $this->dbRequest, clone $this->login );
    }

    throw new APIException( "Class $class does not extend " . Entity::class, JsonResponse::DEBUG );
  }

}

You call the method like this: $factory->makeEntity( Document::class ) and this will give you an object of that class.

This way a change in in the Entity constructor reduced the refactoring effort to a minimum. However, in class that extend EntityI also defined some methods for the relationships between their tables. E.g.:

class DocumentAddressee extends Entity {

    /* ... */

    public static function createFromCustomer( Address $address )
    {
        $self = new DocumentAddressee( clone $address->dbRequest, clone $address->login );

        /* transferring data from Address to $self */

        return $self;
    }

}

(According to verraes.net this is a legit use of static methods as named constructory ).

And methods like these happen quite some times (roughly 1-2 methods per foreign key in a table). Now I'd like to keep those methods, because I can easily access dependend data this way. But I'd also like to keep those constructors to the factory so I don't have to refactor all 100+ Entity-classes when the Entity construcor changes (this might happen if we'll decide to use a QueryBuilder in the future.

Is there already some kind of best practice to handle these methods? Should I propably handle those relationships within the factory or model those relationships in extra classes?

Was it helpful?

Solution

Ideally, you only use the factory to create these objects for consistency. So, in the factory you have something like:

class EntityFactory {

    public function __construct( DatabaseRequest $dbRequest, Login $login )
    {
        $this->dbRequest = $dbRequest;
        $this->login = $login;
    }

    public function create($className)
    {
        $reflection = new \ReflectionClass($className);
        if ($reflection->implementsInterface(Initialisable::class)) {
            throw new APIException( "Please use createFromEntity() to create $className");
        }
        return new $class( clone $this->dbRequest, clone $this->login );
    };

    public function createFromEntity(Entity $entity, $className)
    {
        $newEntity = $this->create($className);
        return $newEntity->initWithEntity($entity);
    }
}

You might want to add initWithEntity() to Entity class, as basic implementation i.e.:

class Entity {

    public function initWithEntity(Entity $entity) {
        $this->dbRequest = clone $entity->dbRequest;
        $this->login = clone $entity->login;
        return $this;
    }
}

And here's the Initialisable interface with updated DocumentAdressee.

interface Initialisable {
    public function initWithEntity(Entity $entity);
}

class DocumentAdressee implements Initialisable {
     // got initWithEntity() from parent class
}

With initWithEntity() there's no need to open access to Entity properties for EntityFactory, so no need to use reflection there. To create a DocumentAdressee object, you use:

$factory->createFromEntity($address, DocumentAdressee::class);

This should work for the problem in your question, where an Entity can have 1 to 1 relationship, e.g. the DocumentAdressee. For classes that have one to many relationship, you'd need to create something like initWithEntities() and pass array of Entitys into that.

As for best practice, there are ORMs that you can use or look at, e.g. Doctrine.

edit

I added Initialisable interface to allow check if the class being created needs to be initialised with an Entity.

Licensed under: CC-BY-SA with attribution
scroll top