Разбивка на страницы, фильтрация и сортировка расширенного поиска с вычисляемыми полями по функции CakePHP

Я ищу лучшее решение для решения этой проблемы. Мне нужно разбить на страницы расширенный поиск квартир со многими параметрами поиска: ключевое слово, категория, дата с, дата до, ценовой диапазон (цена минимальная, максимальная цена) и другие поля ... Если вы выберете две даты, я рассчитываю стоимость аренды каждой квартиры. Для расчета цены я не могу использовать виртуальное поле или один или несколько подзапросов, но мне приходится использовать функцию модели Apartment, потому что она выполняет множество вычислений...

Поэтому я решил использовать обратный вызов afterFind для расчета стоимости аренды и добавления «столбца» rent_price в массив результатов запроса:

public function afterFind($results, $primary){
   if($this->dateFrom && $this->dateTo){
      foreach ($results as $key => $val) {
         if(isset($val['Apartment']['id'])){
            $results[$key]['Apartment']['rental_price'] = $this->calculateRentalPrice($val['Apartment']['id'], $this->dateFrom, $this->dateTo);
         }
      }
   }
   return $results;
}

На контроллере:

$this->paginate = array( ... ); // Array with the query
$apartments = $this->paginate('Apartment');

// EDIT
foreach ($apartments as $key => &$apartment) {
   if($apartment['Apartment']['rental_price'] < $priceMin || $apartment['Apartment']['rental_price'] > $priceMax){
      unset($apartments[$key]);
   }
}
$this->set('apartments', $apartments);

Проблема в том, что теперь я хочу перефильтровать массив $квартир с минимальной и максимальной ценой, но если я напрямую манипулирую $квартирами, у меня возникает проблема с нумерацией страниц. Каков наилучший способ решить такой сценарий?

   // EDIT
   public function calculateRentalPrice($apartmentId, $dateFrom, $dateTo){
      $from = new DateTime($dateFrom);
      $to = new DateTime($dateTo);
      $period = new DatePeriod($from, DateInterval::createFromDateString('1 day'), $to);
      $rentalPrice = 0;
      foreach ($period as $day){
         $date = $day->format("Y-m-d");
         $dailyPrice = $this->ApartmentSeason->getDailyPrice($apartmentId, $date);
         $rentalPrice += $dailyPrice; 
      }
      return $rentalPrice;
   }

   public function getDailyPrice($apartmentId, $date){
      $apartmentSeason = $this->find('first', array(
         'conditions' => array(
            'ApartmentSeason.apartment_id' => $apartmentId,
            "'{$date}' BETWEEN ApartmentSeason.date_from AND ApartmentSeason.date_to", 
            "ApartmentSeason.date_to != " => $date, 
         )
      ));
      $dailyPrice = $apartmentSeason['ApartmentSeason']['price'] / 7;
      return $dailyPrice;
   }

person manzapanza    schedule 04.10.2012    source источник
comment
Уточните проблему с разбиением на страницы и добавьте SQL, который генерирует Cake, когда вы пытаетесь разбить на страницы.   -  person jeremyharris    schedule 04.10.2012
comment
Проблема с paginateCount, которая не возвращает правильное количество страниц/записей.   -  person manzapanza    schedule 04.10.2012
comment
Хорошо, можем ли мы увидеть как SQL, который торт генерирует для результатов, и подсчет, пожалуйста?   -  person jeremyharris    schedule 04.10.2012
comment
Я добавил код, и теперь он должен быть понятен... потому что в SQL есть условие, которое фильтрует результаты на основе арендной_цены.   -  person manzapanza    schedule 04.10.2012
comment
Ну, конечно, ваш счетчик страниц будет отключен, если вы удалите ключи из массива $apartments. Это должны быть условия SQL. Используйте BETWEEN. Вы должны заполнить расчеты afterSave, а не afterFind, чтобы использовать их в SQL-запросе.   -  person jeremyharris    schedule 04.10.2012
comment
Невозможно рассчитать rent_price с помощью AfterSave, потому что это зависит от дат dateFrom и dateTo! Таким образом, rent_price должен рассчитываться динамически на лету с помощью функции. Именно для этого я ищу элегантное решение для решения такого рода проблем, и я не знаю, правильно ли решение с afterFind. Может быть, мне следует использовать пользовательский поиск? Или временная таблица? Это то, что я хочу знать... Спасибо!   -  person manzapanza    schedule 04.10.2012
comment
Если вы не сделаете это с помощью SQL, это станет беспорядочным. Даже если вы обновите количество страниц, количество страниц изменится, потому что вы удалите некоторые из них. Опубликуйте свой calculateRentalPrice. Вы должны быть в состоянии решить это с помощью подзапросов, так как это в основном то, что вы делаете в calculateRentalPrice в любом случае.   -  person jeremyharris    schedule 04.10.2012
comment
Добавил функции расчета стоимости аренды   -  person manzapanza    schedule 05.10.2012


Ответы (1)


Один из способов решить эту проблему — связать модель для каждой цены, попадающей в диапазон дат, а затем суммировать их в виртуальном поле. Возможно, существует более элегантный способ решения этой проблемы, но я не могу придумать способ, в котором не используются процедуры. Это также может быть разделено на многоразовые, тестируемые части.

$from = new DateTime($this->dateFrom);
$to = new DateTime($this->dateTo);
$period = new DatePeriod($from, DateInterval::createFromDateString('1 day'), $to);
$i = 0;
$sums = array();
// bind a new model for each "season" price
foreach ($period as $day) {
  $date = $day->format("Y-m-d");
  // create a new alias
  $alias = "ApartmentSeasonPrice$i";
  $params = array(
    'className' => 'ApartmentSeason',
    'foreign_key' => 'apartment_id',
    'conditions' => array(
      "'{$date}' BETWEEN $alias.date_from AND $alias.date_to",
      "$alias.date_to != " => $date
  );
  // attach the model
  $this->Apartment->bindModel(array(
    'hasOne' => array(
      $alias => $params;
    )
  ));
  // save the column we want to sum
  $sums[] = "$alias.price";
}
// total them up in a virtual field
$this->Apartment->virtualFields['rental_price'] = 'COALESCE(SUM('+implode(',', $sums).', 0))';
// paginate
$this->set('apartments', $this->paginate());
person jeremyharris    schedule 08.10.2012