Я часто делаю PHP-проекты, направленные на соскабливание иерархических данных с веб-страниц и сохранение их в БД (по сути, структурирование данных - подумайте о соскабливании правительственных веб-сайтов, которые имеют данные, но не предоставляют их в структурированном виде). Каждый раз я пытаюсь придумать ООП-дизайн, который позволил бы мне достичь следующего:
Пока я еще не нашел решения, но ближе всего я подошел к этому примерно так:
Я определяю абстрактный класс для контейнеров данных, который будет реализовывать общие функции просмотра деревьев:
abstract class DataContainer {
protected $parent = NULL;
protected $children = NULL;
public function getParent() {
return $this->parent;
}
public function getChildren() {
return $this->children;
}
}
А затем у меня есть реальные контейнеры данных. Представьте себе, что я собираю данные об участии в парламентских сессиях вплоть до уровня "конкретный вопрос на заседании". У меня будет SessionContainer
, SittingContainer
, QuestionContainer
, которые будут расширять DataContainer
.
Каждый из данных сессии, сидения и вопроса берется из разных URL. Оставив в стороне механизм получения содержимого URL, предположим, что мне нужны классы скребков, которые будут принимать контейнеры и DOmDocument для фактического разбора. Поэтому я бы определил общий интерфейс следующим образом:
interface Scraper {
public function scrapeData(DOMDocument $Dom, DataContainer $DataContainer);
}
Затем, каждая сессия, сидение и вопрос будут иметь свои собственные скреперы, которые реализуют этот интерфейс. Но я также хотел бы убедиться, что они могут принимать только те контейнеры, для которых они предназначены. Таким образом, это будет выглядеть так:
class SessionScraper implements Scraper {
public function scrapeData(DOMDocument $DOM, SessionContainer $DataContainer) {
}
}
Наконец, у меня будет общий класс Factory
, который также реализует интерфейс Scraper и просто распределяет скрепы по соответствующим скрепам. Вот так:
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);
}
Это класс, который будет фактически вызываться в коде. Очень похожим образом я мог бы поступить с сохранением в БД - каждый контейнер данных мог бы иметь свой класс DBSaver, который бы реализовывал интерфейс DBSaver. Опять же, все вызовы могут быть сделаны через класс Factory
, который также реализует интерфейс DBSaver.
Все было бы прекрасно, но проблема в том, что классы, реализующие интерфейс, должны реализовывать точную сигнатуру интерфейса. Например, метод SessionScraper::scrapeData
не может принимать только объекты SessionContainer
, он должен принимать все объекты DataContainer
. Но это не так!
Наконец, вопрос:
instanceof
и подобных проверок вместо того, чтобы обеспечивать его с помощью typehinting?Заранее спасибо за все предложения/критику. Я абсолютно счастлив, если кто-то опрокинет этот код на голову, если это необходимо!