Faccio spesso progetti PHP progettati per raschiare dati gerarchici da pagine Web e salvarli nel DB (essenzialmente, strutturare i dati - pensare di raschiare i siti web governativi che hanno i dati, ma non li forniscono in modo strutturato). Ogni volta, cerco di trovare un design OOP che mi avrebbe permesso di raggiungere i seguenti:Progettazione OOP PHP - limitazione dei parametri a classi figlio specifiche durante l'implementazione di interfacce generiche
- sostituire facilmente gli script di parsing HTML attuali con quelli nuovi, nel caso in cui la pagina web originale cambia
- consentono una facile estensioni i dati vengono raschiati e salvati, poiché questi progetti sono anche pensati per gli altri da prendere e sviluppare. Il mio obiettivo è raccogliere i dati "di base", mentre altri potrebbero decidere di includere qualcosa in più, cambiare il modo in cui è salvato ed ecc.
Finora sono ancora riuscito a trovare la soluzione, ma il più vicino che ho ottenuto è qualcosa di simile:
mi definiscono una classe astratta per i contenitori di dati che avrebbe implementare funzioni comuni ad albero di movimento:
abstract class DataContainer {
protected $parent = NULL;
protected $children = NULL;
public function getParent() {
return $this->parent;
}
public function getChildren() {
return $this->children;
}
}
E poi ho i contenitori di dati effettivi. Immagina, sto raschiando i dati sulla partecipazione alle sessioni parlamentari a un livello di "domanda specifica in una seduta". Avrei SessionContainer
, SittingContainer
, QuestionContainer
che estenderebbe tutto il DataContainer
.
Ciascuna sessione, seduta e dati delle domande vengono raschiati da un URL diverso. Lasciando da parte il meccanismo di mettere da parte il contenuto dell'URL, diciamo solo che ho bisogno delle classi di raschiatura, che prenderebbero i contenitori e un DOmDocument per l'analisi vera e propria. Quindi mi sento di definire un'interfaccia generica come questa:
interface Scraper {
public function scrapeData(DOMDocument $Dom, DataContainer $DataContainer);
}
Poi, ciascuna della sessione, seduta e domanda sarebbe hanno le loro ruspe, che implementano l'interfaccia. Ma vorrei anche assicurarmi che possano accettare solo i contenitori a cui sono destinati. Quindi sarebbe simile:
class SessionScraper implements Scraper {
public function scrapeData(DOMDocument $DOM, SessionContainer $DataContainer) {
}
}
Infine, avrei un generico Factory
classe che implementa anche l'interfaccia raschietto e proprio distribuisce la raschiatura di raschietti rilevanti. In questo modo:
public function scrapeData(DOMDocument $DOM, DataContainer $DataContainer) {
//get the scraper from configuration array
$class = $this->config[get_class($DataContainer)];
$craper = new $class();
$class->scrapeData($DOM, $DataContainer);
}
Questa è la classe che verrebbe effettivamente chiamata nel codice. In modo molto simile, potrei occuparmi di salvare in DB: ogni contenitore di dati potrebbe avere la sua classe DBSaver, che implementerebbe l'interfaccia DBSaver. Ancora una volta, tutte le chiamate potrebbero essere effettuate tramite la classe Factory
, che implementerebbe anche l'interfaccia DBSaver.
Tutto sarebbe perfetto, ma il problema è che le classi che implementano l'interfaccia dovrebbero implementare la firma esatta dell'interfaccia. Per esempio. il metodo SessionScraper::scrapeData
non può accettare soloSessionContainer
oggetti, deve accettare tutti gli oggetti DataContainer
. Ma non è destinato a!
Infine, la questione:
- È il mio disegno sbagliato e dovrei essere Strutturare tutto in un modo completamente diverso? (Come?), oppure:
- Il mio progetto è OK, è solo che ho bisogno di applicare i tipi all'interno dei metodi con
instanceof
e controlli simili invece di imporlo tramite typehinting?
Grazie in anticipo per tutti i suggerimenti/critiche. Sono completamente felice con qualcuno che capovolge questo codice sulla sua testa, se necessario!
grazie per la risposta esauriente - innescato un paio di altre idee, anche! Un chiarimento: comprendo correttamente che essenzialmente si suggerisce di avere una classe Data/Container per conservare tutti i dati e identificarla per tipo di proprietà anziché creare classi figlio? Oppure sarebbe sia di tipo proprietà che di classi figlio, solo i raschietti terrebbero conto solo del tipo? – Aurimas
Non conosco i dati in modo specifico, quindi è difficile dirlo. Se i dati sono molto comuni ha solo proprietà differenti, non è necessario creare molte classi di dati, puoi andare con le proprietà dinamiche. Questo è molto meglio per l'applicazione generale più tardi. Per lo più i raschiatori cambieranno, a volte i dati con esso. Dovresti sempre creare una nuova classe di dati solo perché alcuni siti web sono leggermente cambiati. Non bene :) – hakre