composizione della classe php: come implementare la relazione "ha una" nel caso di un DAL

2

Sto imparando l'OOD e le buone pratiche in OOP e mi trovo alle prese con alcuni concetti chiave. Come pratica sto riscrivendo il mio livello di astrazione del database PDO personalizzato che era una singola classe di file con > 2000 linee di codice.

I imparato si dovrebbe usare l'ereditarietà se le classi si trovano in una relazione "è un" e una composizione se hanno una relazione "ha una". La composizione può essere implementata in questo modo, dato che eviterei php's traits (esempio da qui ):

<?php

class Head {
}

class Human {
    private $head;
    public function __construct(Head $head) {
       $this->head = $head;
    }
}

$bob = new Human(new Head);

Buona. Tuttavia, nel mio caso voglio comporre una classe da B ad A, mentre ci possono essere più istanze di B. Precisamente, la classe database principale (A) ha una o più classi table (B). L'inserimento di un oggetto table simile all'oggetto head dell'esempio precedente potrebbe non essere quello che voglio. Successivamente, potrebbe esserci anche una classe select o una insert . Lo faccio solo per esercitarmi e imparo come posso mantenere le mie classi piccole nella dimensione del file. Devo iniettare tutte le dipendenze durante la costruzione e ricomporle? O dovrei istanziarli all'interno della classe database principale e iniettare la connessione alle sottoclassi. La classe del database principale contiene l'oggetto PDO in '$ _connection'.

Q1: qual è il modo migliore per comporre le classi database e table .

Posso pensare a queste strategie.

Strategia n. 1

<?php

class db extends PDO{

  private $_connection;

  public function __construct($dsn){

    $this->_connection = new parent::__construct($dsn);

  }

  public function createTable($def){

     $table = new Table(this->_connection, $def);    

  }

}

Contro:

  • Ho l'operatore new in un metodo che presumo in genere non è l'ideale. Meglio, dovrei iniettare tutte le istanze.
  • Devo dichiarare un metodo createTable nella classe base. Questo spam la mia classe base. Se la funzionalità aumenta, la classe base sarà sempre più grande, il che è ciò che volevo aggirare in primo luogo. Mi piacerebbe essere in grado di chiamare create sull'oggetto tabella come in Table->create() .
  • Non sono sicuro dell'iniezione della connessione alla classe del tavolo. È una buona pratica?

Strategia n. 2

<?php

class db extends PDO{

  private $_connection;
  public  $table;

  public function __construct($dsn, $table){

    $this->_connection = new parent::__construct($dsn);
    $this->table = $table;

  }    

}

$db = new db($dsn, new $Table)
$db->table->create($def);

Contro:

  • Non ho il connection disponibile nella classe Tabella poiché non è né un bambino né la connessione viene iniettata manualmente.

Non penso che le classi db e Table siano in una relazione "è una" e quindi non dovrebbero essere ereditate l'una dall'altra. Ma attualmente mi manca una buona implementazione di composizione.

responsabilità

Ho provato a lavorare per una soluzione, ma ho bisogno di aiuto su quale potrebbe essere la migliore pratica per questo. La composizione, come postata con l'esempio (umano, testa), non sembra proprio qui nel caso di database e tabella. Spero di ricevere risposte utili, anche i link o le parole utili sono i benvenuti, mentre sto imparando e mi sembra difficile avere un momento per entrare nel livello successivo.

    
posta agoldev 02.01.2018 - 20:47
fonte

1 risposta

1

Non sono sicuro del motivo per cui hai un oggetto Table , o perché la tua classe db estende PDO , ma cercherò di spiegare un approccio decente all'accesso al database in un contesto PHP, poiché questo è un'area in cui ho trascorso molto tempo e ho molto interesse.

Approccio DI di base con PDO e istruzioni preparate

Se consideri l'accesso al database per PHP in un contesto PDO / PDOStatement , possiamo davvero ridurlo a poche cose:

  1. Vogliamo essere in grado di aprire e chiudere connessioni a un database
  2. Vogliamo essere in grado di iniziare, eseguire il commit e eseguire il rollback delle transazioni
  3. Vogliamo essere in grado di preparare le dichiarazioni
  4. Vogliamo essere in grado di eseguire SQL come istruzioni preparate fornendo parametri

Il punto 1 è responsabilità di un database accessor e implica la creazione di un PDO. Questa è una relazione "ha una".

I punti 2 e 3, direi, sono anche responsabilità di un accessor del database, tramite il PDO del database accessor. In questo caso, l'accesso al database fungerà da mediatore o facciata.

Il punto 4 è responsabilità di un oggetto di accesso ai dati (DAO).

Il tuo accessor del database implementerebbe un'interfaccia come questa:

interface DatabaseAccessorInterface
{
    public static function beginTransaction(): void;
    public static function commitTransaction(): void;
    public static function dropConnection(): void;
    public static function getConnection(): PDO;
    public static function isActiveTransaction(): bool;
    public static function prepare($sql): PDOStatement;
    public static function rollbackTransaction(): void;
}

... e il tuo oggetto di accesso ai dati astratti un'implementazione sarebbe simile a questa:

abstract class AbstractDAO
{
    private $db;

    public function __construct(DatabaseAccessorInterface $db)
    {
        $this->db = $db;
    }

    protected function db(): DatabaseAccessorInterface
    {
        return $this->db;
    }
}

class UserDAO extends AbstractDAO
{
    public function getAllUsers(): array
    {
        $sql = "SELECT * FROM user";
        $stmt = $this->db()->prepare($sql);
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function getUserByUsername($username): array
    {
        $sql = "SELECT * FROM user WHERE username = :username";
        $stmt = $this->db()->prepare($sql);
        $parameters = [
            ':username' => $username
        ];
        $stmt->execute($parameters);
        return $stmt->fetch();
    }
}

Si noti che questo è un tradizionale approccio "facile" al problema, e non è l'approccio migliore IMO, ma è un ottimo punto di partenza se si sta progettando attorno a PDO e istruzioni preparate.

I modi per aumentare questo approccio includono:

  • Aggiunta di un configuratore, in modo tale che l'accesso al database "abbia una" configurazione
  • Implementazione di una cache di istruzioni preparate
  • Restituzione di un oggetto Result personalizzato anziché fetch o fetchAll() , in modo da poter ottenere informazioni aggiuntive, ad esempio rowCount , errorCode , ecc.

Pensieri dell'autore

Personalmente, credo che pensare a un accesso ai dati in un contesto di database sia strongmente restrittivo al giorno d'oggi. Oggigiorno, i database sono semplicemente una forma di persistenza tra una miriade di scelte. Potremmo connetterci a un database SQL, a un database NoSQL, a un file CSV, a un'API remota, ecc. Penso che sia meglio ampliare l'idea dell'accesso ai dati semplicemente collegandosi a e interrogando su un database, a quello di accesso a un remoto archivio dati che potrebbe assumere molti moduli.

Quando guardiamo in questo modo, scopriamo che ci sono varie parti in gioco:

  • Un Accessor che è responsabile di una connessione a un particolare tipo di origine dati
  • Un AccessorConfiguration che contiene le informazioni di configurazione per un Accessor
  • Un PersistenceStrategy che contiene informazioni su quale artefatto nell'origine dati sarà la destinazione per query e comandi
  • Calls , che sono equivalenti alle stringhe di query SQL
  • Responses , che contiene informazioni sull'esecuzione di Calls (ad esempio numero di righe / colonne restituite, informazioni sull'errore, ecc.)
  • Un OperationRepository che è responsabile dell'esecuzione di Calls e restituisce Responses
  • Un OperationCache che memorizza Responses rispetto a Calls

Una volta avviata questa strada, scopriamo che le interfacce diventano molto più semplici nella loro lingua e eviti di inquinare il tuo codice con un gergo specifico del database:

interface PersistenceStrategyInterface
{
    /**
     * Deletes a DataEntity from the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface
     */
    public function delete(DataEntityInterface $dataEntity);

    /**
     * Gets a DataEntity from the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface|null
     */
    public function get(DataEntityInterface $dataEntity);

    /**
     * Saves the DataEntity to the persistence mechanism.
     *
     * @param DataEntityInterface $dataEntity
     * @return ResponseInterface
     */
    public function save(DataEntityInterface $dataEntity);
}

interface OperationRepositoryInterface
{
    /**
     * Gets the Response from the OperationRepository.
     *
     * @param CallInterface $call The Call that generates the Response.
     * @param AccessorInterface $accessor The Accessor that the Call is made against.
     * @return ResponseInterface
     */
    public function response(CallInterface $call, AccessorInterface $accessor);
}

Dichiarazione di non responsabilità: questo codice è tratto da framework Circle314 , che attualmente sto sviluppando

    
risposta data 03.01.2018 - 00:17
fonte

Leggi altre domande sui tag