В 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 и правим там:
и тестим:
ляпота! =)