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 Entity
I 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?
-
Why don't you go procedural instead of fighting OOP? I ask this because PHP you have both options.Dellowar– Dellowar2017年01月25日 15:12:37 +00:00Commented Jan 25, 2017 at 15:12
1 Answer 1
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 Entity
s 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.
-
Why does that EntityFactory have static methods?Tekay37– Tekay372017年01月27日 10:59:07 +00:00Commented Jan 27, 2017 at 10:59
-
@Tekay37 no particular reason, I edited the code to use your exampleimel96– imel962017年01月27日 22:54:34 +00:00Commented 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– Tekay372017年01月29日 15:40:29 +00:00Commented 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– imel962017年01月30日 06:40:20 +00:00Commented Jan 30, 2017 at 6:40
Explore related questions
See similar questions with these tags.