Ereditarietà quando si segue il modello di repository in PHP

4

Sto provando a creare un'applicazione PHP utilizzando il pattern di repository ma non sono sicuro di come dovrei implementare il metodo save .

Ho una classe astratta chiamata ItemRepository che ha il seguente metodo:

abstract class ItemRepository
{
    public function save(Item $item);
}

Dove Item è anche una classe astratta.

Ora voglio implementare la classe MovieRepository che estende ItemRepository . Qui voglio salvare istanze della classe Movie che estende Item .

Anche se lo fa in questo modo in PHP

// MovieRepository.php
class MovieRepository extends ItemRepository
{
    public function save(Movie $movie)
    {
        ...        
    }
}

dà il seguente errore

Declaration of MovieRepository::save() should be compatible with ItemRepository::save(Item $item)

Qual è il modo giusto per farlo?

    
posta Oskar Persson 13.09.2015 - 02:24
fonte

5 risposte

1

Rilascia i tipi di elemento / film dal metodo save ():

abstract class ItemRepository
{
    public abstract function save($item);
}

class MovieRepository extends ItemRepository
{
    public function save($movie)
    {
        ...        
    }
}

PHP è principalmente il linguaggio tipizzato dinamicamente e mentre capisco il desiderio di usare i tipi il più possibile, in questo caso il sistema di tipo PHP non è abbastanza strong per esprimere ciò che ti serve. In altri linguaggi (Java, C ++), potresti esprimerlo con generici, che PHP non ha.

    
risposta data 16.09.2015 - 13:04
fonte
0

Non puoi. Una sottoclasse dovrebbe supportare gli stessi metodi della classe genitore, senza imporre ulteriori restrizioni. In questo modo se incontri una variabile di tipo ItemRepository , non hai bisogno di sapere quale sottoclasse è; puoi sempre chiamare save e passarlo a Item .

Se non vuoi questo, puoi aggiungere un metodo diverso, come saveMovie(Movie $movie) . Ma potresti voler riconsiderare se vuoi una sottoclasse.

    
risposta data 15.09.2015 - 07:23
fonte
0

A meno che le classi ItemRepository e MovieRepository siano effettivamente correlate, non è necessaria una classe genitore. Invece, vai con MovieRepository implementando un'interfaccia:

interface IMovieRepository
{
    function save(Movie $movie);
}

class MovieRepository implements IMovieRepository
{
    public function save(Movie $movie) {
        // ...
    }
}

Qualsiasi luogo hai bisogno del repository del film, usa invece l'interfaccia. Di seguito è riportato un controller di esempio che utilizza l'iniezione del costruttore:

class MoviesController
{
    private $movies;

    public __construct(IMovieRepository $movies = null) {
        $this->movies = $movies || new MovieRepository();
    }

    public function create($params) {
        $movie = new Movie();
        // Populate $movie based on $params

        $this->movies->save($movie);
    }
}

Se possibile, creo una classe BaseRepository che non implementa alcun metodo CRUD e contiene solo funzionalità comuni per tutte le classi di repository. Il salvataggio di un'entità in un'altra cambierà, quindi non sono correlati e non dovrebbero condividere una classe genitore comune.

    
risposta data 18.09.2015 - 21:07
fonte
0

Potresti trarre beneficio dallo studio di Doctrine2. Anche se utilizza il "Pattern di repository", utilizza anche il "Pattern Mapper dei dati".

Il seguente è un esempio di utilizzo del gestore di entità:

/* $movieRepository = MovieRepository -> extends EntityRepository -> implements ObjectRepository */
if (null !== $movie = $movieRepository->findOneBy(array('name' => 'Mad Max'))) {
    $movie = new Movie();
    $movie->setName('Mad Max');

    /* $entityManager = EntityManager -> implements ObjectManager */
    $entityManager->persist($movie);
}

$movie->setViews($movie->getViews() + 1);

$entityManager->flush();

Si noti qui che Doctrine2 definisce due classi principali (una astratta).

abstract class EntityRepository implements ObjectRepository
{
    ...
}

class EntityManager implements ObjectManager
{
    ...
}

Tradizionalmente il repository viene utilizzato solo per effettuare query, ad esempio per le selezioni, raramente per gli aggiornamenti o gli inserimenti. L'interfaccia ObjectRepository definisce i metodi di selezione comuni:

  • find($id)
  • findBy(array $criteria)
  • findOneBy(array $criteria)

Tuttavia, il gestore di entità ha i metodi relativi alla persistenza:

  • persist($entity)
  • flush($entity = null)

Il gestore di entità non richiede mai un tipo specifico di entità oltre al "range" di classi registrate come mappate al suo database.

Se vuoi creare la tua libreria di persistenza, puoi utilizzare le interfacce Doctrine\Common\Persistance per aiutarti a progettarla.

  • ObjectRepository
  • ObjectManager

Un motivo per cui potresti scegliere di persist entità e flush tutto in una volta, è creare una transazione coerente che "finalizza" il processo. Contro "salvare" ciascuna entità individualmente durante il processo, che aumenterebbe gradualmente la quantità di IO nell'applicazione, influendo sulle prestazioni.

TL; DR

Incorporare il pattern Data Mapper nella progettazione e migrare la persistenza lontano dai repository e invece incapsularla in un singolo gestore per il database. Quindi limita il repository alle query di selezione.

    
risposta data 22.09.2015 - 10:37
fonte
0

Ho intenzione di indovinare, che quello che stai cercando di fare è avere un'implementazione comune di save() ma esposta con il tipo corretto per ogni tipo di repository? (Non hai dichiarato save() come abstract nel tuo ItemRepository , quindi suppongo che tu abbia un corpo del metodo in là?)

Dato che il tipo di argomento per save() in diversi tipi di repository cambierà, non stiamo parlando dello stesso metodo save() - in altre parole, la denominazione del metodo save() in ogni tipo di repository sta andando essere una convenzione, ma sono metodi diversi, poiché ad es save(Movie $movie) non è compatibile con save(Item $item) , perché Movie è un Item , ma un Item non è necessariamente un Movie .

In base a ciò, suggerirei di estrarre un metodo di salvataggio interno in una classe base astratta.

abstract class AbstractItemRepository {
    protected function saveItem(Item $item) {
        // ...
    }
}

class ItemRepository extends AbstractItemRepository {
    public function save(Item $item) {
        $this->saveItem($item);
    }
}

class MovieRepository extends AbstractItemRepository {
    public function save(Movie $movie) {
        $this->saveItem($movie);
    }
}

In altre parole, metti funzionalità condivise in una classe base astratta, ma lascia la definizione dell'interfaccia pubblica fino all'implementazione individuale.

Detto questo, vedi la risposta di @GregBurghardt che sottolinea:

Unless the ItemRepository and MovieRepository classes are actually related, then you don't need a parent class

In altre parole, se la funzionalità di salvataggio non è un'implementazione comune, non ha senso tentare di astrarre questo, al di là di cercare di applicare una convenzione di denominazione dei metodi; ma non è esattamente ciò che l'astrazione è per.

    
risposta data 03.11.2015 - 12:59
fonte