1

Тема: Розділи та підрозділи (БД/Масиви)

ТЗ: Вивести список розділів форуму, під кожним розділом повинні виводитися його підрозділи.
Наприклад:

Розділ: Програмування
Підрозділи:
PHP
C++
Java
Python
Розділ: Верстка
Підрозділи:
HTML
CSS
Design

Використовується паттерн MVC, тому в View шаблон повинні передатися тільки змінні (масиви) для перебору.

В моделі написав такий метод.

    public function readForumsParts(){
        
    $resultForum = []; //масив для даних розділу
    $resultPart = []; //масив для даних підрозділу
    
        //робимо вибірку розділів форуму    
    $forums = $this->db->query("SELECT * FROM forums");
        $forums->setFetchMode(PDO::FETCH_OBJ);
        
         while($forum = $forums->fetch()) //збираємо результати вибірки розділів
         {
            $forum->name = htmlspecialchars($forum->name);
            $resultForum[] = $forum; //записуємо обєкт даних поточного розділу в масив
            
                        //робимо вибірку підрозділів по поточному forum->id розділа
            $parts = $this->db->prepare("SELECT * FROM forum_part WHERE id_forum = ?");
            $parts->execute([$forum->id]);
            
            $parts->setFetchMode(PDO::FETCH_OBJ);
                    
            while($part = $parts->fetch()){ //збираємо результати вибірки підрозділів
                
                $resultPart[] = $part; //записуємо обєкт даних поточного підрозділу в масив
            
            }
            
         }
        //повертаємо масив з двома масивами всередині
        return [$resultForum, $resultPart];
        
    }


В результаті роботи методу маємо наступний масив.

Array
(
    [0] => Array
        (
            [0] => stdClass Object
                (
                    [id] => 1
                    [name] => dfshsdfhdfh
                )

            [1] => stdClass Object
                (
                    [id] => 2
                    [name] => тест =)
                )

            [2] => stdClass Object
                (
                    [id] => 3
                    [name] => тестовий розділ :)
                )

            [3] => stdClass Object
                (
                    [id] => 4
                    [name] => тееееест :)
                )

        )

    [1] => Array
        (
            [0] => stdClass Object
                (
                    [id] => 1
                    [id_forum] => 2
                    [name] => ок)))))))
                )

            [1] => stdClass Object
                (
                    [id] => 2
                    [id_forum] => 2
                    [name] => паоваовпо
                )

        )

)

І приклад того, як я це все вививоджу.. :)

        $array = $model->readForumsParts(); //отримуємо вище вказаний масив
        
        foreach($array[0] as $forum){ //перебираємо масив розділів форуму
            
            echo '<b>'.$forum->name.'</b></br>'; //виводимо розділ
            
            foreach($array[1] as $part){ //перебираємо підрозділи поточного розділу
                
                if($forum->id == $part->id_forum) //якщо поточний id розділу = id поточного підрозділу
                    echo $part->name.'</br>'; //то виводимо підрозділ
                
            }
            
            
        }

Ок, все працює.

Але як можна вирішити оптимальніше?

Можливо є ще якесь рішення з самим SQL запитом (зробити якось один запит для вибірки) у звязці з PHP, або ж оптимальніше з'єднати масиви? (не просто злити в один). . . .

Подякували: 221VOLT1

2

Re: Розділи та підрозділи (БД/Масиви)

По-перше, в моделі не повинно бути якоїсь іншої логіки, окрім вибірки даних та передачі результату назад в контролер. А вже в контролері крутіть тими даними як хочете.

По-друге, базові SQL-джоїни досить прості, у вашому випадку це може бути щось типу:

select
  f.id as id_forum
  ,f.name as forum_name
  ,fp.id as id_subforum
  ,fp.name as subforum_name
from forums as f
  left join forum_part as fp
    f.if = fp.id_forum
order by
  f.id
  ,fp.id

Я тут використовую left join, бо у правій таблиці (forum_part) ще може не бути підфорумів, а left join каже що "вибирати усі дані з лівої таблиці, а з правої - лише ті значення, які співпадають по полям зв’язування  лівої таблиці".

Простий же джоїн (без слова left) виводив би дані, лише якщо у обох таблицях є співпадіння по полям зв’язування.

Такий запит оптимальний для БД, бо дані вибираються за один раз, але у PHP-скрипті вам прийдеться трохи погратись щоб не виводити дублів назв форумів. Грубо кажучи це може бути щось типу:

    public function readForumsParts()
    {
      /**
       * Робимо вибірку розділів та підрозділів, причому змінюємо назви полів щоб не було дублів.
       * Зверніть увагу на сортування (order by f.id, fp.id) - це потрібно щоб дані не перемішувались
       */
      $forums = $this->db->query(
        "select
          f.id as id_forum
          ,f.name as name_forum
          ,fp.id as id_subforum
          ,fp.name as name_subforum
        from forums as f
          left join forum_part as fp
            f.if = fp.id_forum
        order by f.id, fp.id
      ");

      // Тепер об’єкт $forums можна передавати у в’юху
      $forums->setFetchMode(PDO::FETCH_OBJ);

      return $forums;
    }

А у в’юсі вже перебираємо отриманий об’єкт:

       // отримуємо об’єкт
        $forums = $model->readForumsParts();
        
        /**
         * Ініціалізуємо змінну,
         * вона нам потрібна лише щоб уникнути дублів назв форумів при виводі
         */
        $prev_forum_name = '';
        
        //збираємо результати вибірки розділів
        while( $forum = $forums->fetch() )
        {
          // Якщо у попередній ітерації була точно така сама назва форуму, не виводимо її
          if( $prev_forum_name !== $forum->name_forum )
          {
            $prev_forum_name = $forum->name_forum;
            
            // виводимо розділ
            echo '<b>'.$forum->name_forum.'</b></br>'; 
          }
          
          // виводимо розділ
          echo '<b>'.$forum->name_subforum.'</b></br>';
        }

П.С. Коментарі зазвичай пишуться перед змінною, виразом, функцією, класом... до яких вони стосуються (у вас коментарі йдуть вже після змінної...)

Подякували: VTrim, leofun012

3

Re: Розділи та підрозділи (БД/Масиви)

У моделі я нічого лишнього здається і не чіпаю. Перебір я для прикладу показав.
Коментарі писав прямо тут у текстовому полі :) (як вже є)
А так, дякую, спробую розібратися.

4

Re: Розділи та підрозділи (БД/Масиви)

Ви в моделі перебираєте отримані дані з БД, формуєте з них два масиви. Цього не повинно бути в моделі, нехай контролер робить потрібні маніпуляції.

5

Re: Розділи та підрозділи (БД/Масиви)

Чомусь я ніде не чув, що метод з моделі повинен повертати лише дані з бд (хоча там і так дані з бд,тільки у вигляді двох масивів), і ніяк інакше.

Контроллер у мене звертається до моделі,отримує масив (з двома вбудованими) передає в вьюшку.

6

Re: Розділи та підрозділи (БД/Масиви)

Ну взагалі-то, поділ функціональності між контролером і моделями - часто спірна тема.

Коли я кажу що у модель не потрібно закладати певну логіку обробки вже отриманих даних з БД, то керуюсь логікою: контролер є головнокомандуючим він повинен контролювати весь процес та давати прості задачі для моделі "дай мені дані і не думай за те, що я з ними зроблю".

Грубо кажучи - модель повинна бути драйвером до БД...

7

Re: Розділи та підрозділи (БД/Масиви)

Ну не знаю, мені здається це нормальним.
А контроллер за отримання даних від користувача, редіректи,etc.

8

Re: Розділи та підрозділи (БД/Масиви)

Трішки поправив ваш запит.

select
          f.id as id_forum
          ,f.name as name_forum
          ,fp.id as id_subforum
          ,fp.name as name_subforum
        from forums as f
          left join forum_part as fp ON
            f.id = fp.id_forum
        order by f.id, fp.id

Результат
http://image.prntscr.com/image/01cb2d55d599401890d8b205607e8311.png

Навіть не знаю що з цим робити..
Наскільки поганий мій варіант у першому пості? (Бо з вибіркою в один запит все рівно доведеться робити костиль в правильною вибіркою в PHP, і думаю що цей костиль буде ще гіршим аніж той,що зараз)

9 Востаннє редагувалося ktretyak (15.07.2016 22:32:59)

Re: Розділи та підрозділи (БД/Масиви)

Мій варіант хоч і має костиль у вигляді дублів назв форумів, але ваш запит генерує велику кількість запитів за один вивід, точніше - кількість запитів до БД буде дорівнювати кількості різних форумів + 1.

Відповідно якщо брати той тестовий приклад, який ви показали в попередньому коментарі, то ваш код 5 разів звертатиметься до БД. Це перше.

Друге - нащо ви заганяєте дані в масиви, якщо можна у в’юсі фетчити об’єкти з БД і зразу ж їх виводити? Причому у вас і вивід неоптимальний, бо за кожним рядком з форуму, ви перебираєте весь масив підфорумів, навіть якщо частина підфорумів вже виведена.

Ну і на останок подивіться на скільки лаконічно та читабельно виглядає мій варіант, навіть без коментарів:
Model:

public function readForumsParts()
{
  $forums = $this->db->query(
    "select
      f.id as id_forum
      ,f.name as name_forum
      ,fp.id as id_subforum
      ,fp.name as name_subforum
    from forums as f
      left join forum_part as fp
        on f.id = fp.id
    order by f.id, fp.id
  ");

  $forums->setFetchMode(PDO::FETCH_OBJ);

  return $forums;
}

View:

$forums = $model->readForumsParts();
$prev_name_forum = '';

while( $forum = $forums->fetch() )
{
  if( $prev_name_forum !== $forum->name_forum )
  {
    $prev_name_forum = $forum->name_forum;

    echo '<b>'.$forum->name_forum.'</b></br>'; 
  }

  echo '<b>'.$forum->name_subforum.'</b></br>';
}

10

Re: Розділи та підрозділи (БД/Масиви)

Справа в тому, що view на те і view, що це шаблон і туди повинні вставлятися вже згенеровані дані.
Повинні бути тільки невеликі вставки php код через рідний або інший шаблонізатори.
Ніякої роботи з бд там точно не повинно бути.

11

Re: Розділи та підрозділи (БД/Масиви)

Оце ви називаєте "роботою з БД"?

$forum = $forums->fetch()

12

Re: Розділи та підрозділи (БД/Масиви)

$forums = $model->readForumsParts();

Далі, доступ до моделі з вьюшки?

13

Re: Розділи та підрозділи (БД/Масиви)

VTrim написав:

$forums
Далі, доступ до моделі з вьюшки?

Ні, це я зайве написав, насправді контролер повинен передавати $forums у в’юху.

Порівняйте наші варіанти виводу у в’юсі:

// Ваш варіант "невеликих вставок PHP"

$array = $model->readForumsParts();

foreach($array[0] as $forum)
{
  echo '<b>'.$forum->name.'</b></br>'; //виводимо розділ

  foreach($array[1] as $part)
  {
    if($forum->id == $part->id_forum)
    echo $part->name.'</br>';
  } 
}

// Мій варіант "великих вставок"

$prev_name_forum = '';

while( $forum = $forums->fetch() )
{
  if( $prev_name_forum !== $forum->name_forum )
  {
    $prev_name_forum = $forum->name_forum;

    echo '<b>'.$forum->name_forum.'</b></br>'; 
  }

  echo '<b>'.$forum->name_subforum.'</b></br>';
}

14

Re: Розділи та підрозділи (БД/Масиви)

VTrim написав:

$forums = $model->readForumsParts();

Далі, доступ до моделі з вьюшки?

А, так це ваш баг, я просто його ще не видалив =)... Чи то просто ви показали як приклад запуску методу...

15

Re: Розділи та підрозділи (БД/Масиви)

Кількість вставок приблизно така ж.
В першому пості я сказав, що це приклад для наглядності. В самій вьюшці перебираються тільки два масиви.

Ваш спосіб я підтримую, але тільки зі сторони одного sql запиту.

16

Re: Розділи та підрозділи (БД/Масиви)

ktretyak написав:
VTrim написав:

$forums = $model->readForumsParts();

Далі, доступ до моделі з вьюшки?

А, так це ваш баг, я просто його ще не видалив =)... Чи то просто ви показали як приклад запуску методу...

Приклад запуску.

17

Re: Розділи та підрозділи (БД/Масиви)

VTrim написав:

Кількість вставок приблизно така ж.
В першому пості я сказав, що це приклад для наглядності. В самій вьюшці перебираються тільки два масиви.

Ваш спосіб я підтримую, але тільки зі сторони одного sql запиту.

Ок, якщо ви зможете обгрунтувати чим поганий мій варіант у в’юхі, я навіть можу погодитись з вами.

18

Re: Розділи та підрозділи (БД/Масиви)

Тим, що використовується метод об'єкту бд, його теж треба туди передати.
Якби не довелося дотримуватися якихось правил mvc, то я б просто на льоту вивів всі розділи, підрозділи. Без ніяких масивів та if'ів. І єдиним мінусом були б тільки лишні запити до бд.

19

Re: Розділи та підрозділи (БД/Масиви)

Метод не треба передавати, він йде разом з об’єктом. Більше того - ви передаєте у в’юху з контролера лише змінну $forums і все.

20

Re: Розділи та підрозділи (БД/Масиви)

Я її і мав на увазі, передавання обєкту.