3

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?

1
  • Why don't you go procedural instead of fighting OOP? I ask this because PHP you have both options.
    – Dellowar
    Commented Jan 25, 2017 at 15:12

1 Answer 1

2

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.

4
  • Why does that EntityFactory have static methods?
    – Tekay37
    Commented Jan 27, 2017 at 10:59
  • @Tekay37 no particular reason, I edited the code to use your example
    – imel96
    Commented Jan 27, 2017 at 22:54
  • Ok, so one last question: This init___() are supposed to be the first method called on a freshly created object. Should this use of an init-method be enforced to render misuse impossible or would it be enough to document its purpose?
    – Tekay37
    Commented Jan 29, 2017 at 15:40
  • You can add check to see if init__() has been called in the classes that needs to be initialised. I don't think it's possible to enforce usage of init__() without adding constructor to all the classes that need it. I edited to add check in EntityFactory to make sure init__() will be called where it's needed.
    – imel96
    Commented Jan 30, 2017 at 6:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.