Gestisce i costruttori denominati con pattern factory

3

Nel mio attuale progetto sto rifattorizzando il codice per ottenere un DBAL. Ho un class Entity che è la classe base per tutte le classi che modellano una tabella di database. Quindi ci sono diverse classi che ereditano da Entity come Document , Article e così via.

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

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

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

}

Poiché tutte queste classi hanno lo stesso costruttore __construct( DatabaseRequest $dbRequest, Login $login ) e non voglio gironzolare per quei due paramenter, ho anche fatto questo:

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 );
  }

}

Chiami il metodo in questo modo: $factory->makeEntity( Document::class ) e questo ti darà un oggetto di quella classe.

In questo modo un cambiamento nel costruttore Entity ha ridotto lo sforzo di refactoring al minimo. Tuttavia, in classe che estenda Entity ho anche definito alcuni metodi per le relazioni tra le loro tabelle. Per esempio:.

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;
    }

}

(Secondo verraes.net questo è un uso legittimo di metodi statici come costruttivo).

E metodi come questi avvengono abbastanza spesso (approssimativamente 1-2 metodi per chiave esterna in una tabella). Ora mi piacerebbe mantenere quei metodi, perché posso facilmente accedere ai dati dipendenti in questo modo. Ma mi piacerebbe anche mantenere questi costruttori in fabbrica, quindi non devo rifattorizzare tutte le 100 classi Entity quando cambia il costrutto Entity (questo potrebbe accadere se decidessimo di usare un QueryBuilder in futuro.

Esiste già una sorta di best practice per gestire questi metodi? Dovrei gestire in modo propizio queste relazioni all'interno della fabbrica o modellare quelle relazioni in classi extra?

    
posta Tekay37 17.01.2017 - 12:09
fonte

1 risposta

2

Idealmente, si usa solo la fabbrica per creare questi oggetti per coerenza. Quindi, in fabbrica hai qualcosa come:

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);
    }
}

Potresti voler aggiungere initWithEntity() a Entity class, come implementazione di base i.e.:

class Entity {

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

Ed ecco l'interfaccia Initialisable con DocumentAdressee aggiornato.

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

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

Con initWithEntity() non è necessario aprire l'accesso alle proprietà Entity per EntityFactory , quindi non è necessario utilizzare la riflessione lì. Per creare un oggetto DocumentAdressee , usi:

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

Questo dovrebbe funzionare per il problema nella tua domanda, in cui un'entità può avere una relazione 1 a 1, ad es. il codice%. Per le classi con una relazione uno a molti, devi creare qualcosa come DocumentAdressee e passare la matrice di initWithEntities() s in quella.

Per quanto riguarda le migliori pratiche, esistono ORM che puoi utilizzare o esaminare, ad es. Dottrina.

modifica

Ho aggiunto l'interfaccia Entity per consentire di verificare se la classe che si sta creando deve essere inizializzata con un'entità.

    
risposta data 25.01.2017 - 23:10
fonte