CRUD / Основы GET запросов

В url помимо основной части адреса типа http://localhost:9007/space-object/1 часто встречается дополнительные элементы, которые добавляются в конец после знака вопроса:

http://localhost:9007/space-object/1?sort=title&filter=Галактика

это так называемые get параметры. Они представляют собой набор ключей и значений, которые разбиваются знаком амперсанда &

Используются как правило для уточнения поведения базового запроса, для всяких сортировок фильтраций и т.п.

У нас сейчас верхняя навигация не задействована. Точнее там есть какие-то пункты, но они уже не работают.

Давайте будем использовать ее, чтобы можно было выбрать какие типы объектов я хочу посмотреть. Например, у меня есть туманности, а есть галактики. И я хочу сделать чтобы кликая на одну ссылку показывались туманности, а на другую галактики.

Но сначала надо подготовить данные. Давайте добавим в базу поле, в котором будет указана группа к которой принадлежит объект.

Идем в phpMyAdmin и добавляем поле

назову его type

теперь надо его заполнить у всех объектов. Для быстрого заполнения можно просто два раза тыкать на поле и вписывать значения:

Делаем навигацию

Теперь надо сделать так чтобы при формировании строки навигации выводился список возможных типов. Так как меню должно присутствовать на всех страницах, есть два способа, правильный и быстрый =)

Быстрый способ

идея его проста, в twig мы можем определить переменную, которая будет доступна из любого шаблона. Для этого в index.php надо сделать следующее

// ...
$twig->addExtension(new \Twig\Extension\DebugExtension());

$pdo = new PDO("mysql:host=localhost;dbname=outer_space;charset=utf8", "root", "");

// создаем запрос к БД
$query = $pdo->query("SELECT DISTINCT type FROM space_objects ORDER BY 1");
// стягиваем данные
$types = $query->fetchAll();
// создаем глобальную переменную в $twig, которая будет достпна из любого шаблона
$twig->addGlobal("types", $types);

$router = new Router($twig, $pdo);
// ...

теперь можно пойти в базовый шаблон __layout.twig и добавить там цикл по типам:

<!DOCTYPE html>
<html lang="en">
<head>
    <!-- ... -->
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container">
            <a class="navbar-brand" href="#"><i class="fas fa-meteor"></i></i></a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
                <!-- Оставил ссылку на главную -->
                <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="/">Главная</a>
                </li>
                <!-- остальные заменил на цикл по типам -->
                {% for type in types %}
                    <li class="nav-item">
                        <!-- type.type -- выдает тип, 
                               а добавление |title делает первую букву с слова большой  -->
                        <a class="nav-link" href="#">{{ type.type|title }}</a>
                    </li>
                {% endfor %}
            </ul>
            </div>
        </div>
    </nav>
    <div class="container pt-3 pb-3">
        {% block content %}
        {% endblock %}
    </div>
</body>
</html>

смотрим:

то есть значения из базы подцепились, правда ссылки пока еще не работают.

Чем плох этот способ? С точки зрения шаблона – все прекрасно. А вот с точки зрения кода, не очень. У нас index.php – это входная точка приложения, которая управляет высокими материями, роутером, подключением пакетов, создания соединения с базой данных, активация генератора шаблонов, ну и как бы все это связывает между собой.

И вот эта попытка сделать запрос к БД и передать что-то в twig, выглядит как будто мы на концерте симфонической музыке дуем в свисток. В общем, не уместно, плюс невозможно тестировать.

Поэтому

Правильный способ

Значит, более корректный подход — это создать базовый контроллер для нашего приложения (не фреймворка – фреймворк, в идеале должен быть не зависим от темы проекта) и уже все остальные контроллеры наследовать от него.

Давайте это сделаем. Создаем файлик, я его назову BaseSpaceTwigController.php и загоню код который был в index.php, но с небольшими правками

ну а из index.php убираем

ну и заменяем везде на TwigBaseController на BaseSpaceTwigController

работает так же

но архитектурно более грамотно.

Используем навигацию как фильтр

Сейчас я хочу сделать так, чтобы тыкая на элемент навигации срабатывал один и тот же контроллер. Но чтобы он понимал, что я хочу видеть объекты только какого-то специфического типа.

Как это сделать?

Для этого надо чтобы к ссылке, ведущей на главную страницу добавились параметры запроса. Делается это следующим образом. Идем в __layout.twig и добавляем в href у элементов навигации параметры:

{% for type in types %}
    <li class="nav-item">
        <!-- добавил href="/?type={{ type.type }}" -->
        <a class="nav-link" href="/?type={{ type.type }}">{{ type.type|title }}</a>
    </li>
{% endfor %}

глянем как это выглядит

то есть у нас в запросе появляется часть ответственная за параметры, но роутер почему-то перестает распознавать путь

Почему так происходит?

Чтобы понять, давайте добавим вывод $_SERVER["REQUEST_URI"] в роутере

class Router {
    // ...

    public function get_or_default($default_controller) {
        $url = $_SERVER["REQUEST_URI"];
        print_r($url); // добавил вывод
        // ...
    }
    // ...
}

смотрим страницу

то есть PHP не отделяет за нас сам адрес, от параметров адреса. К счастью имеется функция которая умеет это делать. Зовется parse_url – знает, как из строки url вытаскивать всякие отдельные куски, на вроде схемы, адреса сайта, полного адреса и прочих элементов, которых на самом деле не так уж и мало. Проверим как она работает:

public function get_or_default($default_controller) {
    $url = $_SERVER["REQUEST_URI"];

    $path = parse_url($url, PHP_URL_PATH); // вытаскиваем адрес
    echo $path; // выводим

    // ...
}

О как:

Но помимо самого адреса нам нужны еще и параметры.

А с параметрами все проще. Набор параметров в адресной строке называется параметрами GET запроса и доступны они через специальную переменную $_GET, давайте глянем:

public function get_or_default($default_controller) {
    $url = $_SERVER["REQUEST_URI"];

    $path = parse_url($url, PHP_URL_PATH);
    echo $path;

    echo "<pre>"; // чтобы красивее выводил
    print_r($_GET); // выведем содержимое $_GET
    echo "</pre>";
    // ...
}

смотрим:

мы можем через & добавить еще пару параметров, например, http://localhost:9007/?type=галактика&sort=123&my_array[0]=c&my_array[1]=b и глянуть что произойдет:

то есть этот $_GET хранит в удобном виде параметры из адресной строки.

Чиним роутер

Так давайте сначала починим роутер, чтобы он игнорил параметры и тестировал на совпадение только сам адрес, вот так:

Подключаем фильтр

А теперь идем в MainController и правим там:

и тестим:

ляпота! =)

3

Переделать ObjectController.php так чтобы он содержал логику и для ObjectImageController.php и ObjectInfoController.php, то есть принятие решения о том показывать картинку, краткую информацию или полную принималось по get параметрам. Например:

  • http://localhost:9007/space-object/1 – общая информация
  • http://localhost:9007/space-object/1?show=image – показывает картинку
  • http://localhost:9007/space-object/1?show=info – показывает полную информацию

всех ссылки обновить соответствующим образом. Избавиться от контроллеров ObjectImageController.php и ObjectInfoController.php