Я бы сказал, следуя Single Responsibility Principle.
У вас есть SportsCard, которая ничего не делает сама по себе. Это всего лишь контейнер данных.
class SportsCard
{
protected $_price;
protected $_sport;
public function __construct($price, $sport)
{
$this->_price = $price;
$this->_sport = $sport;
}
public function getPrice() { return $this->_price; }
public function getSport() { return $this->_sport; }
}
Карты спорта должны храниться в карточке. Если вы думаете о CardBook, то он ничего не делает, для хранения SportsCards (не Покемон или Magic The Gathering карты), за исключением, так что давайте просто сделать это сделать:
class CardBook extends SplObjectStorage
{
public function attach($card)
{
if ($card instanceof SportsCard) {
parent::attach($card);
}
return $this;
}
public function detach($card)
{
parent::detach($card);
}
}
Хотя нет ничего плохого добавляя методы Finder к набору SportsCard, вы также не хотите, чтобы внутри него находилась логика метода поиска. Это хорошо до тех пор, пока у вас есть только
public function findByPrice($min, $max);
public function findBySport($sport);
Но второй вы также добавить
public function findByPriceAndSport($sport, $min, $max);
вы либо дубликат кода или итерацию по коллекции дважды. К счастью, PHP предлагает элегантное решение для фильтрации коллекций с итераторами, поэтому давайте ответственность за фильтрацию в отдельные классы, а также:
abstract class CardBookFilterIterator extends FilterIterator
{
public function __construct($cardBook)
{
if ($cardBook instanceof CardBook || $cardBook instanceof self) {
parent::__construct($cardBook);
} else {
throw new InvalidArgumentException(
'Expected CardBook or CardBookFilterIterator');
}
}
}
Этот класс просто делает, что ребенок классы принимают только ребенок себя или CardBooks. Это важно, потому что мы позже получаем доступ к методам SportsCard в реальных фильтрах и потому, что CardBooks может содержать только SportsCards, мы убеждаемся, что итерационные элементы предоставляют эти методы. Но давайте посмотрим на конкретный фильтр:
class SportCardsBySportFilter extends CardBookFilterIterator
{
protected $sport = NULL;
public function filterBySport($sport)
{
$this->_sport = $sport;
return $this;
}
public function accept()
{
return $this->current()->getSport() === $this->_sport;
}
}
FilterIterators работают путем расширения класса и модификации он принимает метод() с пользовательской логики. Если метод не возвращает FALSE, текущему итерированному элементу разрешено передавать фильтр. Итераторы могут быть сложены, поэтому вы добавляете Filter on Filter сверху, каждый из которых заботится только об одной части фильтрации, что делает ее очень удобной и расширяемой. Второй фильтр:
class SportCardsByPriceFilter extends CardBookFilterIterator
{
protected $min;
protected $max;
public function filterByPrice($min = 0, $max = PHP_INT_MAX)
{
$this->_min = $min;
$this->_max = $max;
return $this;
}
public function accept()
{
return $this->current()->getPrice() > $this->_min &&
$this->current()->getPrice() < $this->_max;
}
}
Здесь нет никакой магии. Только метод accept и setter для критериев. Теперь, чтобы собрать его:
$cardBook = new CardBook;
$cardBook->attach(new SportsCard('10', 'Baseball'))
->attach(new SportsCard('40', 'Basketball'))
->attach(new SportsCard('50', 'Soccer'))
->attach(new SportsCard('20', 'Rugby'))
->attach(new SportsCard('30', 'Baseball'));
$filteredCardBook = new SportCardsBySportFilter($cardBook);
$filteredCardBook->setFilterBySport('Baseball');
$filteredCardBook = new SportCardsByPriceFilter($filteredCardBook);
$filteredCardBook->filterByPrice(20);
print_r(iterator_to_array($filteredCardBook));
И это даст:
Array (
[4] => SportsCard Object (
[_price:protected] => 30
[_sport:protected] => Baseball
)
)
Комбинирование фильтров из внутри CardBook является ветер в настоящее время. Нет дублирования кода или двойной итерации. Сохраняете ли вы методы Finder внутри CardBook или CardBookFinder, через которые вы можете проходить CardBooks, зависит от вас.
class CardBookFinder
{
protected $_filterSet;
protected $_cardBook;
public function __construct(CardBook $cardBook)
{
$this->_cardBook = $this->_filterSet = $cardBook;
}
public function getFilterSet()
{
return $this->_filterSet;
}
public function resetFilterSet()
{
return $this->_filterSet = $this->_cardBook;
}
Эти методы просто убедитесь, что вы можете создать новую FilterSets и что у вас есть CardBook внутри Finder, прежде чем использовать один из методов поиска:
public function findBySport($sport)
{
$this->_filterSet = new SportCardsBySportFilter($this->getFilterSet());
$this->_filterSet->filterBySport($sport);
return $this->_filterSet;
}
public function findByPrice($min, $max)
{
$this->_filterSet = new SportCardsByPriceFilter($this->getFilterSet());
$this->_filterSet->filterByPrice(20);
return $this->_filterSet;
}
public function findBySportAndPrice($sport, $min, $max)
{
$this->findBySport($sport);
$this->_filterSet = $this->findByPrice($min, $max);
return $this->_filterSet;
}
public function countBySportAndPrice($sport, $min, $max)
{
return iterator_count(
$this->findBySportAndPrice($sport, $min, $max));
}
}
И там вы идете
$cardBookFinder = new CardBookFinder($cardBook);
echo $cardBookFinder->countBySportAndPrice('Baseball', 20, 100);
$cardBookFinder->resetFilterSet();
foreach($cardBookFinder->findBySport('Baseball') as $card) {
echo "{$card->getSport()} - {$card->getPrice()}";
}
Не стесняйтесь приспосабливаться и улучшать свою собственную карточку.
+1 для поднятия вероятного изменения данных между видами спорта. –