Тут настраиваем laragon для удобной разработки на PHP
Бэкенд
Разбираемся с обработкой простых веб-запросов и делаем простой многостраничный сайт.
Прежде чем начать делать это задание вам надо придумать себе тему про что вы будете делать сайт.
Это могут быть персонажи, или фильмы, или какие-то музыкальные произведения, инструменты, автомобили, деревья, цветы в общем что вам любо. Главное, чтобы этому объекту можно было сопоставить картинку и какое-нибудь описание. Пока вам хватит двух экземпляров вашей темы.
Я выбрал тему космических туманностей. У меня в качестве подопытных: Галактика Андромеда и Туманность Ориона
Доработать сайт:
- Добавить дополнительные страницы с информацией об объекте и с картинкой объекта
- Под новые страницы сделать отдельные файлы, которые будут подключаться со страницы объекта
- На главную страницу вывести ссылки на все страницы
- На дополнительных страницах должно быть видно какой пункт меню выбран
Должно получится что-то такое:
Подключаем шаблонизатор Twig для работы с разметкой более грамотно
Переписать сайт из 2-го задания на рельсы Twig
- Должно быть минимум два базовых шаблона шаблона, всего шаблонов будет порядка 6 штук,
- общий шаблон
- под главную страницу (наследует общий),
- под страницу объекта (наследует общий),
- под страницу с картинкой объекта (наследует шаблон объекта)
- и под страницу с информацией о первом экземпляре объекта (наследует шаблон объекта)
- и под страницу с информацией о втором экземпляре объекта (наследует шаблон объекта)
- Использовать цикл для вывода пунктов меню на главной странице
- [не обязательно] Использовать цикл для вывода пунктов в навигации
Доработать свое приложение, путем реализации контроллеров для всех страниц.
ОБЯЗАТЕЛЬНОЕ ДЛЯ ВСЕХ
Реализовать макрос для вывода кнопки в активном состоянии. Интерфейс вызова макроса:
{{ btn("Текст на кнопке", "url", true, "warning") }}
должно сгенерировать разметку
<a href="url" class="btn btn-warning">Текст на кнопке</a>
если третий параметр – false
, то выводить
<a href="url" class="btn btn-link">Текст на кнопке</a>
если четветрый параметр не указан, то для {{ btn("Текст на кнопке", "url", true) }}
выводить кнопку в стиле primary
<a href="url" class="btn btn-primary">Текст на кнопке</a>
Во всех местах вашего приложения где выводится кнопка выводить ее через макрос.
Под элемент списка на главной странице создать подшаблон и подключать его через include.
Примеры работы с include и macro можно глянуть в документации https://twig.symfony.com/doc/3.x/tags/include.html и https://twig.symfony.com/doc/3.x/tags/macro.html
Задание не обязательное, если вы зайка. А если вы проштрафились, то обязательное и сделать его надо до того, как начнете делать 4-ое задание этого модуля.
-
Необходимо скопировать верстку навигации с одного из популярных сайтов и натянуть ее на бутстраповскую навигацию вашего приложения.
-
Стили оформить в scss (использование переменных, миксинов и иерархии стилей обязательно)
Примеры сайтов для копирования:
можно свой вариант, главное, чтобы у пунктов меню был hover эффект, и четкое активное состояние
Как делать задание 1
И так, для того чтобы понять, как работает веб будем использовать PHP.
Не потому, что других альтернатив нет, а потому что в плане способа разобраться с тем как происходит обработка запроса, наверное, самый простой вариант.
И так идем на сайт https://laragon.org/download/ и качаем портабельную версию:
распаковываем в папку
получится такое:
теперь скачаем PHP версии 7, ну чтобы быть более-менее современными. Идем на https://windows.php.net/download#php-7.4 и качаем:
сохраняем внутрь папке laragon-portable в подпапках \bin\php
:
и тоже распаковываем
собственно, всё. Мы файлы нужные собрали и можно уже попробовать позапускать сервер.
Запускаем файлик laragon.exe
Laragon – это некое подобие виртуальной машины, которая позволяет работать с любой папкой как будто папка лежит на сервере и к ней идут запросы из вне. Более того в laragon можно добавлять всякие дополнительные модули для работы с Базами, с кешами, утилиты в общем все что душе угодно.
после запуска увидим такой интерфейс
нажимаем кнопку Start All, если будут выскакивать запросы на открыть доступ, то просто пропускаем их.
Теперь можно открыть сайт в браузере по ссылке http://localhost
давайте зайдем и посмотрим, что лежит в папке:
там всего один файлик, этот файлик запускается, когда мы заходим на http://localhost.
На самом деле там сервер настроен так что когда мы идем на http://localhost нас на самом деле редеректит на адрес http://localhost/index.php
Давайте глянем, что внутри файла. Откроем index.php с помощью Visual Studio Code
И в самом начале файла увидим:
<?php
if (!empty($_GET['q'])) {
switch ($_GET['q']) {
case 'info':
phpinfo();
exit;
break;
}
}
?>
пока не будем вникать как это работает. Но если сильно не уточнять, главное отличие php файла от html файла в том, что мы можем делать вставки php кода прямо в html, причем они будут проинтерпретированы на стороне сервера и клиенту придется уже простой html
Если проскроллить дальше там в основном идет простой html, с небольшими вставками php кода, например:
Тут опять есть вставки php кода, но более локальные. Например <?php print($_SERVER['SERVER_SOFTWARE']); ?>
заменяется на nginx/1.14.0
а <?php print ($_SERVER['DOCUMENT_ROOT']); ?>
заменяется на путь к папке где лежит файлик index.php
.
Давайте глянем в html который приходит с сервера (нажать Ctrl+U в браузере):
<body>
<div class="container">
<div class="content">
<div class="title" title="Laragon">Laragon</div>
<div class="info"><br />
nginx/1.14.0<br />
PHP version: 5.4.9 <span><a title="phpinfo()" href="/?q=info">info</a></span><br />
Document Root: D:/_MMK/_PHP/laragon-portable/www<br />
</div>
<div class="opt">
<div><a title="Getting Started" href="http://laragon.org/?q=getting-started">Getting Started</a></div>
</div>
</div>
</div>
</body>
то есть в html все эти <?php ... ?>
исчезают и заменяются на простой текст.
Так как не факт, что вы будете сидеть на своем компе, а устанавливать laragon по 10 раз не хочется. Давайте просто создадим отдельную папку под наш проект и будем с ней работать.
А саму папку вообще лучше таскать на флешке ну или закинуть в гит и оттуда подтягивать. И так создаем на папку
откроем ее через Visual Studio Code
и созадим в ней файлик index.php
и загоним в него
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>Всем привет</div>
<div>Версия PHP: <?php print phpversion(); ?></div>
</body>
</html>
так, теперь настроим чтобы laragon раздавал эту папку. Для этого тыкаем правой кнопкой мыши в окне laragon и выбираем Select another
и выбираем
и теперь идем на http://localhost, и проверяем
ура! =)
Давайте теперь еще переключим версию PHP, не зря ж мы ее качали. Снова тыкаем правой кнопкой в окне laragon
перегрузим страницу
Красота! Все настроено, можно теперь изучить предмет поглубже! =)
Как делать задание 2
Начнем с истории. В стародавние времена, когда web еще был на стадии своего бурного развития условно начало двухтысячных и PHP был главным игроком на рынке языков для веба, писали на нем как попало.
Именно тогда сформировалось негативное отношение к пхпешниками как в основном низкоквалифицированным программистам. Порог входа был очень низким, сайты представляли собой наборы несвязанных php файлов, а на код было страшно смотреть.
К счастью веб развивался, развивался PHP, вместе с ними росло и требование к разработчикам, а вслед за ними подвезли и требования к проектированию веб приложений. Архитектура MVC и сотоварищи стали править балом.
Поэтому мы постараемся по-быстрому пройти этап наивного программирования на PHP и как можно быстрее уйти на архитектуру MVC
Добавляем новую страничку
Давайте глянем на наш проект. У нас сейчас там один файлик. А так-то, обычно, на сайтах много разных ссылок. Давайте попробуем зайти на какую-нибудь ссылку, например, http://localhost/hello
В ответ получим ошибку 404
которая означает что файла нет. Что конечно предсказуемо, но уныло.
Так что давайте добавим файлик hello.php
вот с таким содержимым:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
Я новая страничка!!!1111 =)
</body>
</html>
пробуем снова http://localhost/hello
хм, все равно 404
а если такой url: http://localhost/hello.php
О! Заработало! То есть получается мы после http://localhost/ должны указывать путь к php файлу. Ну ладно…
Добавляем картинку
А давайте попробуем картинку добавить. И причем так, чтобы она у нас на сервере лежала. Вот такую, например,:
сохраняем ее в папку где лежит файл, причем лучше подпапку images
создать, вот так:
теперь давайте подключим ее на новой странице, для этого надо указать ее путь относительно папки с проектом. И еще путь ОБЯЗАТЕЛЬНО должен начинаться с прямого слеша /
вот так:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- … -->
</head>
<body>
Я новая страничка!!!1111 =)
<br>
<img src="/images/025_picture-47918.gif" alt="">
</body>
</html>
проверяем:
ура! =)
Только возникает тогда вопрос почему php понял что эта картинка и не стал интерпретировать файлик, мы ведь даже можем его открыть по прямой ссылке http://localhost/images/025_picture-47918.gif
более того мы можем даже открыть папку: http://localhost/images/
а это, товарищи, между прочим дыра в безопасности. Неужели все это делает php?
Настраиваем Nginx
А вот и нет! На самом деле это делает не php. На самом деле когда мы пишем http://localhost/images/ запросы идут не к php, а сначала к веб-серверу Nginx (engine x — по-русски произносится как энджи́нкс, но я почему-то говорю нгинкс… как пишется так и слышится)
Это специальная программа, которая в общем не предназначена исполнять какой-нибудь код, ее главная задача перенаправлять запросы разным интерпретаторам и раздавать файлы. И делает она это настолько виртуозно что может одновременно обслуживать десятки тысяч соединений и раздавать тысячи файлов. За что ее все нежно любят *_*
Давайте заглянем в настройки этого самого nginx
Так как nginx может обслуживать сразу несколько сайтов, то в блоке sites-enabled можно выбрать какой именно сайт мы хотим настроить. Так как мы работаем только с одним проектом и никаких дополнительных настроек не производили, то мы выбираем 00-default.con, там мы увидим:
в общем первое что нам надо сделать это отключить autoindex, можно поставить значение off, либо просто закомментить строку, вот так:
и теперь надо перезапустить nginx
проверяем:
Forbidden – то есть запрещено, то что нам и надо =)
А теперь сделаем еще одну вещь которая может показаться странной, но мы сделаем так чтобы все запросы, если файл не найден, шли в index.php, для этого поправим строчку с try_files:
пропишем там
try_files $uri $uri/ /index.php$is_args$args;
снова перезапускаем nginx
попробуем теперь зайти, например, на такую страницу http://localhost/page/about
по идее нас должно переправить на index.php,
проверяем:
красота! =)
Узнаем URL запроса
Что же нам это дало? А то что мы теперь управляем всеми запросами, которые идут не напрямую к обычным файлам с помощью одного index.php, а это в свою очередь дает нам возможность более централизовано руководить приложением.
Давайте вообще посмотрим, как мы можем использовать информацию о ссылке внутри нашего файлика index.php
И так, каждый раз, когда запускается php файл ему доступен список предопределенных переменных https://www.php.net/manual/ru/reserved.variables.php
давайте глянем что в них оказывается, когда мы будем открывать страничку с разными адресами, добавим в index.php:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- … -->
</head>
<body>
<?php print_r($_SERVER) ?> <!-- добавил вывод информации о переменной $_SERVER -->
</body>
</html>
получим такую нечитаемую кашу
на самом деле php сформировал нам ответ в красивом виде, но так как браузер игнорит всякие переносы то выглядит это уродливо.
Можно даже глянуть код страницы (Ctrl+U) и увидеть
К счастью в html есть специальный тег pre
которые учитывает все переносы и отступы, я кстати использую его для вывода кода во всех своих статьях. В общем просто берем и оборачиваем тегом нашу php вставку.
<body>
<pre>
<?php print_r($_SERVER) ?>
</pre>
</body>
Запускаем и скроллим пока не найдем пункт REQUEST_URI
если присмотреться в нем написано тоже самое что и в строке бразуера.
Давайте попробуем вывести это значение отдельно.
$_SERVER
– это словарик, то есть у него есть ключи и значения, для доступа используется оператор квадратных скобок []
, вот так:
<body>
<pre>
<?php print_r($_SERVER["REQUEST_URI"]) ?>
</pre>
</body>
смотрим:
прикольно! =) Но что нам с этого?
Делаем менюшку
Попробуем сделать меню. У меня допустим будет сайт о галактиках и туманностях во вселенной. Пусть у меня будет три странички. Например:
- Главная
- Галактика Андромеда
- Туманность Ориона
Давайте запилим меню.
Подключаем бутстрап
Я буду использовать бутстрап. Идем на сайт https://getbootstrap.com/docs/5.0/getting-started/introduction/
и копируем себе css в head файла index.php
как-то так получится
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
</head>
<body>
<?php print_r($_SERVER["REQUEST_URI"]) ?>
</body>
</html>
теперь нам надо вставить меню навигации, опять же его можно скопипастить с сайта bootstrap, идем сюда https://getbootstrap.com/docs/5.0/components/navbar/#nav
и пихаем в body
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="#">Navbar</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="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Features</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Pricing</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
</div>
</div>
</nav>
<?php print_r($_SERVER["REQUEST_URI"]) ?>
если присмотреться, то мы увидим, что за пункты в меню отвечает вот этот блок
давайте перепишем его так чтобы остались только нужные нам:
получится вот так:
чтобы наш /hello/words
выровнялось по навигации, обернем нашу инструкцию в класс .container
, это специальный класс в бутстрапе, который добавляет отступы по краям. Причем он респонсивный, то есть на больших экранах отступы больше, на маленьких – меньше, на телефоне вообще могут отсутствовать.
Вот так:
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<!-- ... -->
</nav>
<div class="container">
<?php print_r($_SERVER["REQUEST_URI"]) ?>
</div>
теперь уже симпатичнее
Кстати вот эта надпись Navbar
это на самом деле место под лого сайта, давайте подключим fontawesome
и запихаем туда какую-нибудь иконку,
<head>
<!-- ... -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet" />
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<!-- тут вместо Navbar загнал <i class="fas fa-meteor"></i> -->
<a class="navbar-brand" href="#"><i class="fas fa-meteor"></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">
<!-- ... -->
</ul>
</div>
</div>
</nav>
<div class="container">
<?php print_r($_SERVER["REQUEST_URI"]) ?>
</div>
</body>
проверяем:
давайте теперь как-нибудь сделаем так, чтобы тыкая на разные пункты меню у нас появлялся разные текст внизу, в принципе тут все просто. Достаточно прописать url у каждого пункта меню. Прописываем:
попробуем теперь потыкать:
класс! =)
То есть формально мы вроде страницы не переключаем, но выглядит будто бы мы таки переключаем. Пусть вас это не смущает, так работают 90% сайтов в мире
Управляем содержимым
Давайте теперь попробуем чего-нибудь вывести, например, на главной страницу будет список ссылок на все страницы (ну типа продублируем навигацию), а на страницах туманностей будут картинки
Способ №1
Это самый неправильный способ, никогда так не делайте, но в принципе он сам естественный. Мы просто будем проверять значение переменной $_SERVER["REQUEST_URI"]
и в зависимости от значения выводить тот или иной кусок.
PHP нас не ограничивает в таком написании, у него есть поддержка if
, и делается это так:
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<!-- ... -->
</nav>
<div class="container">
<?php if ($_SERVER["REQUEST_URI"] == "/") { ?>
Вы на главной странице! =)
<?php } elseif ($_SERVER["REQUEST_URI"] == "/andromeda") { ?>
Тут мы вам расскажем о волшебной Галактике Андромеда
<?php } elseif ($_SERVER["REQUEST_URI"] == "/orion") { ?>
Был значит один кот, и носил он галактику в поясе Ориона
<?php } ?>
</div>
</body>
то есть логику мы оборачиваем в <?php ... ?>
а вывод в браузер помещаем внутрь как будто это тело if
ну типа работает, но читать очень тяжело, да и писать долго.
Способ №2
По убогости примерно, как первый, но писать меньше. Делается значит так, вместо того чтобы писать кучу php оберток делается одна и в нее все помещается. Выглядит так
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<!-- ... -->
</nav>
<div class="container">
<?php
if ($_SERVER["REQUEST_URI"] == "/") {
echo "Вы на главной странице! =)<br>";
echo "<b>И разметку могу принтовать</b>";
} elseif ($_SERVER["REQUEST_URI"] == "/andromeda") {
echo "Тут мы вам расскажем о волшебной Галактике Андромеда";
} elseif ($_SERVER["REQUEST_URI"] == "/orion") {
echo "Был значит один кот, и носил он галактику в поясе Ориона";
}
?>
</div>
В общем, в PHP есть специальный оператор echo
, который вставляет указанный текст в содержимое html ответа ну или правильнее сказать в содержимое web-ответа, ведь ответ это не всегда html файлик, может быть и json какой-то или вообще картинка, а то и простой файл.
Кстати никто не мешает нам объявлять переменные и использовать их:
<?php
$url = $_SERVER["REQUEST_URI"]; // у переменной в PHP слева ставим $
if ($url == "/") { // и при обращении тоже
echo "Вы на главной странице! =)<br>";
echo "<b>И разметку могу принтовать</b>";
} elseif ($url == "/andromeda") {
echo "Тут мы вам расскажем о волшебной Галактике Андромеда";
} elseif ($url == "/orion") {
echo "Был значит один кот, и носил он галактику в поясе Ориона";
}
?>
может казаться не очень привычным юзать знак доллара, но есть и плюсы. В PHP можно очень быстро вставить переменную в строку, например, так:
<?php
$url = $_SERVER["REQUEST_URI"];
echo "Вы на странице: $url, будьте внимательны!<br>"; // вместо url подставится значение $url
if ($url == "/") {
echo "Вы на главной странице! =)<br>";
echo "<b>И разметку могу принтовать</b>";
} elseif ($url == "/andromeda") {
echo "Тут мы вам расскажем о волшебной Галактике Андромеда";
} elseif ($url == "/orion") {
echo "Был значит один кот, и носил он галактику в поясе Ориона";
}
?>
и вроде как здорово, но ведь если надо будет вывести хотя бы пару десятков строк читать это станет не возможным. Поэтому:
Способ №3
Самый грамотный способ. По крайне мере если используем базовый PHP. Идея такая: взять и создать под страницы отдельные файлы и в зависимости от адреса выводит их содержимое.
Создадим отдельную папку, назовем ее views, создадим в ней три файлика и загоним в файлике содержимое страниц, вот так:
а теперь сделаем так чтобы в нашем index.php в зависимости от url выводилось содержимое этих файлов, для этого будем использовать еще один специальный оператор require, который просто берет и вставляет то что находится внутри указанного после оператора файла:
<div class="container">
<?php
$url = $_SERVER["REQUEST_URI"];
echo "Вы на странице: $url, будьте внимательны!<br>";
if ($url == "/") {
require "views/main.php";
} elseif ($url == "/andromeda") {
require "views/andromeda.php";
} elseif ($url == "/orion") {
require "views/orion.php";
}
?>
</div>
проверяем:
работает так же, но теперь мы можем создавать содержимое любой сложности в отдельных файликах! =)
Настраиваем безопасность
Еще одни тонкий момент. Оно у нас конечно все работает, и даже ошибок, но на самом деле у нас в коде присутствует уязвимость. Потенциальный злоумышленник может получить доступ к отдельным частям нашей программы.
Например, он может зайти по пути http://localhost/views/orion.php и увидеть содержимое файла orion.php
И казалось бы чего страшного то? Но дело в том, что при разработке реального приложения, у вас в файлах может лежать какая-та информация которую вы не хотели бы показывать стороннему пользователю. Или какой-то скрипт которые будучи выполненным не из index.php может повести себя не предсказуемо.
Поэтому все современные веб-движки рекомендуют создавать отдельную папку public
в которой будут лежать index.php, всякие картинки, стили и все такое. Давайте настроим наше приложение чтобы оно работало таким образом:
попытка открыть http://localhost нам выдаст
теперь надо настроить laragon чтобы он в качестве root папки использовал папку public, идем в laragon,
выбираем папку public
и вроде как и всё, но проблема в том что laragon сбросил наши настройки nginx, давайте зайдем туда и глянем:
смотрим
и так, чтобы эти настройки не терялись, у вас по сути два варианта, первый: это вы просто таскаете папку c laragon на флешке и с нее запускаетесь, и второй это создать настройку nginx под себя и просто переключать на нее.
Делается это так:
в открывшейся папке копируете 00-default.conf
и называете как-нибудь, чтобы идентифицировать файлик и свою принадлежность к нему
затем правите следующим образом
и тыкаем перегрузить
я выбрал порт 9007
, и теперь я буду открывать свое приложение по адресу http://localhost:9007
проверяем:
хохо! Работает! Только ошибки сыпятся. Но оно и понятно у нас ведь в index.php
пути относительно index.php строятся, а надо чтобы он ходил на папку выше. Поэтому идем в index.php
и правим
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<!-- ... -->
</nav>
<div class="container">
<?php
$url = $_SERVER["REQUEST_URI"];
echo "Вы на странице: $url, будьте внимательны!<br>";
if ($url == "/") {
require "../views/main.php"; // << добавил ../, что означает ищи файл в папке на уровень выше
} elseif ($url == "/andromeda") {
require "../views/andromeda.php"; // << и тут
} elseif ($url == "/orion") {
require "../views/orion.php"; // и здесь
}
?>
</div>
</body>
</html>
тестируем
ляпота! =)
И самое замечательное что теперь никто не сможет посмотреть содержимое отдельного php файла
Такие дела *______*
Как делать задание 3
Проверка строки на частичное соответствие
Кстати вам скорее всего потребуется проверять проверка совпадения строки не на полное соответствие, а, например, что какая-та строка просто совпадает частично, например, так мы можем проверить что url начинается со слова андромеда
if (preg_match("#^/andromeda#", $url)) {
require "../views/andromeda.php";
}
Тут мы используем регулярное выражение. Вот так может понятнее:
Создание иерархии файлов
Организовывая файлы в проекте, надо придерживаться следующего подхода:
1) index.php – это точка входа вашего веб-приложения
2) только в файле index.php содержится теги html, body, шапка с навигацией
3) файл index.php разбит на две основные области
в свою очередь каждый файлик andromeda или orion должен быть ответственен только за свою разметку и иметь примерно следующую структуру
Подсветка активного элемента
В задании там вот так сделано
ну, во-первых, тут используется элемент бутстрап https://getbootstrap.com/docs/5.0/components/navs-tabs/#pills
идея такая,
то есть надо добавлять этот класс динамически. С точки зрения кода нам надо запихать условие внутрь разметки:
Получится что когда url равен ссылке на картинку, то надо добавить класс active. И оно даже работать будет. Но читать это не реально =)
Поэтому первое что мы можем сделать это вынести логику в отдельный блок php, где-нибудь повыше, как-нибудь так:
<?php
// объявили переменную, которая True если адрес совпадает с адресом с страницы с картинкой
$is_image = $url == "/andromeda/image";
?>
<ul class="nav nav-pills">
<li class="nav-item">
<!-- а тут теперь проверяем значение этой переменной -->
<a class="nav-link <?php if ($is_image) { ?>active<?php } ?>" href="/andromeda/image">
Картинка
</a>
</li>
<!-- ... -->
</ul>
уже лучше. Но читается все равно еще не очень. Давайте вместо двух блоков <?php … ?>сделаем один с echo
:
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link <?php if ($is_image) { echo "active"; } ?>" href="/andromeda/image">
Картинка
</a>
</li>
<!-- ... -->
</ul>
а это можно упростить вообще до такого:
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link <?= $is_image ? "active" : '' ?>" href="/andromeda/image">
Картинка
</a>
</li>
<!-- ... -->
</ul>
тут используется специальный упрощенный блок <?= ... ?>
в него можно запихать какое-нибудь выражение и он сработает как
<?php echo $is_image ? "active" : '' ?>
Элементы на главной страницы
Там я использовал https://getbootstrap.com/docs/5.0/components/list-group/ для списка
а для названий страницы кнопочки https://getbootstrap.com/docs/5.0/components/buttons/
Как делать задание 4
Идем дальше по дорожке развития PHP приложения.
Современные веб приложения очень трепетно относятся к организации кода.
За что в первую очередь критиковали PHP, а за то что он по сути не отделяет вёрстку от кода А при росте сложности приложения превращается в лапшу.
Для решения этой проблему решили, что пусть php файлы содержат только код. А разметка пусть где-то отдельно лежит.
Давайте попробуем так сделать
Создадим новый проект:
то есть две папки public
и views
. Папка views
пустая, а в папке public
наша точка входа index.php
не забудем переключить папку на новый проект
И так, мы решили, что в php файлов не должно быть верстки. Значит верстку будем пихать в папку views
, будем использовать просто html файлы.
Возьмем картинки mermaid.jpg uranus.png, и положим их в папку images
Ну и файлики сделаем
и вот вроде бы как все хорош. Отдельно файлики, отдельно код.
Но теперь возникает другая проблема. А как мне сделать навигацию, да так чтобы поменьше кода писать? Или как мне например передать данные в html файл и чтобы он их обработал, но чтобы php код писать не пришлось.
В общем, для решения этой проблемы были придуманы так называемый Templates engines
, то есть шаблонизаторы по-русски.
Помимо того, что шаблонизаторы упрощают написание динамических элементов (как например добавление класс active в прошлой задачке) плюс ко всему они добавляют возможность строить иерархию шаблонов, то есть реализовывать наследование.
Ну типа, есть у вас базовый шаблон страницы где присутствует допустим навигация, блок под общее содержимое и футер. И вот вы хотите сделать страницу с новостями. Вы просто создаете файлик указываете в нем что наследуетесь от базового шаблона и переопределяете отображение блока под содержимое. И усе =О
Twig
Мы будем использовать один из самых популярных шаблонизаторов, а именно Twig https://twig.symfony.com
чтобы начать его использовать, надо его сначала установить. Для установки дополнительных пакетов в php используется специальная утилита composer
. Она скачивает дополнительные библиотечки из интернета и кладет их в папку vendor
В общем, тыкаем на Terminal
это запустит нам консольку вместе с настроенными переменными окружения
тут есть важный момент, путь который написан в этой строке должен соответствовать папке в которой лежит ваш проект
если по какой-то причине там какой-то другой путь. То вам надо перейти в консольке в папку с вашим проектом.
Для этого найдите папку с вашим проектом. Скопируйте путь к этой папке
и введите в консольке команду
cd П:\уть\к\папке\с\вашим\проектом
в моём случае будет
cd C:\Users\m\Desktop\php_01
Теперь можно попробовать написать composer и посмотреть, что выведет:
тут информация как библиотечкой управлять, вдруг кому полезна будет
Тут нам важно одно, так как composer по умолчанию ставит пакеты в текущую папку, то в целях безопасности надо обязательно выйти из папки public, если у вас путь оканчивается на public
то вызовете следуюущую команду чтобы подняться на уровень выше:
cd ..
теперь идем на сайт twig https://twig.symfony.com/doc/3.x/intro.html#installation и смотрим как правильно установить twig
там вот что написано:
то есть надо ввести команду
composer require "twig/twig:^3.0"
правда в политехе интернет работает через прокси, поэтому сначала надо настроить прокси в консоли, для этого введите сначала команду утсановки переменной окружения, а потом уже команду установки
set http_proxy=172.27.100.5:4444
composer require "twig/twig:^3.0"
и ждем пока установится
и смотрим что у нас появилось в папке
давайте теперь попробуем воспользоваться twig.
Открываем index.php и пишем
<?php
// подключаем пакеты которые установили через composer
require_once '../vendor/autoload.php';
// создаем загрузчик шаблонов, и указываем папку с шаблонами
// \Twig\Loader\FilesystemLoader -- это типа как в C# писать Twig.Loader.FilesystemLoader,
// только слеш вместо точек
$loader = new \Twig\Loader\FilesystemLoader('../views');
// создаем собственно экземпляр Twig с помощью которого будет рендерить
$twig = new \Twig\Environment($loader);
$url = $_SERVER["REQUEST_URI"];
// ..
кстати если у вас подсвечивается красным имена классов поставьте плагин PHP Intelephense
Рендерим с помощью twig
идея в общем такая, мы вместо того чтобы подключить файлы с помощью require будем вызывать команды twig а он будет уже собственно ренедрить за нас файлы, делается так:
<?php
// ...
$url = $_SERVER["REQUEST_URI"];
if ($url == "/") {
// это убираем require "../views/main.html";
echo $twig->render("main.html");
} elseif (preg_match("#/mermaid#", $url)) {
// и это тоже require "../views/mermaid.html";
echo $twig->render("mermaid.html");
} elseif (preg_match("#/uranus#", $url)) {
// и вот это require "../views/uranus.html";
echo $twig->render("uranus.html");
}
пока особых преимуществ не видно, ну только что ../views/
не надо писать
можно потыкать как работает, по идее должно работать как обычно:
сразу ставим себе плагин для работы с twig
Теперь, сделаем базовый шаблон разметки, назовем его __layout.twig
загоняем туда стандартную разметку html (через ! + Tab
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- но с небольшой правкой, это будет место под индивидуальный код страниц -->
{% block content %}
пустота
{% endblock %}
</body>
</html>
теперь давайте наследуем этот шаблон нашими страницами. Сначала идем в main.html
и правим там:
{% extends "__layout.twig" %}
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
проверяем:
получаем ошибку что у нас в main.html разметка находится во вне какого-нибудь блока который определен в шаблоне. Давайте запихаем ее вот так:
{% extends "__layout.twig" %}
{% block content %}
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
{% endblock %}
ну рендерится теперь
но все равно пока не впечатляет… о чем сыр-бор то?
Терпенье мой друг! =О
Давайте теперь тоже самое сделаем с остальными файлами, с mermaid.html
{% extends "__layout.twig" %}
{% block content %}
<img src="/images/mermaid.jpg" style="width: 300px;"/>
{% endblock %}
и с uranus.html
{% extends "__layout.twig" %}
{% block content %}
<img src="/images/uranus.png" style="width: 300px;"/>
{% endblock %}
а теперь сделаем магию, возьмем и в нашем базовом шаблоне добавим навигацию:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div><!-- добавил -->
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
</div>
{% block content %}
пустота
{% endblock %}
</body>
</html>
смотрим:
Ооо! Меню теперь везде есть, на главной странице даже дважды, давайте там чего-нибудь напишем:
{% extends "__layout.twig" %}
{% block content %}
Я главная страница
{% endblock %}
а в шаблоне добавим ссылку на главную страницу
<div>
<a href="/">Главная</a> <!-- добавил -->
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
</div>
{% block content %}
пустота
{% endblock %}
О как:
Передаем данные в шаблон
Прежде чем что-то делать, давайте переименуем наши шаблоны в twig формат, чтобы нормально работал плагин для Twig
и в index.php поменяем:
$url = $_SERVER["REQUEST_URI"];
if ($url == "/") {
echo $twig->render("main.twig");
} elseif (preg_match("#/mermaid#", $url)) {
echo $twig->render("mermaid.twig");
} elseif (preg_match("#/uranus#", $url)) {
echo $twig->render("uranus.twig");
}
в любой шаблон мы можем передать данные в виде словарика. Ну, например, у нас страница всегда называется Document
давайте будем передавать название страницы в нашем роутере (я роутером называю наши ifы которые решают какой шаблон рендерить)
if ($url == "/") {
echo $twig->render("main.twig", [
"title" => "Главная" // в квадратных скобках создаем словарик с ключем title, значением "Главная"
]);
} elseif (preg_match("#/mermaid#", $url)) {
echo $twig->render("mermaid.twig", [
"title" => "Русалка"
]);
} elseif (preg_match("#/uranus#", $url)) {
echo $twig->render("uranus.twig", [
"title" => "Уран"
]);
}
а теперь подключим эту значение в шаблоне, идем в __layout.twig
и вместо Document вставляем:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title> <!-- заменил Document на {{ title }}-->
</head>
<body>
<!-- ... -->
то есть в двойных фигурных скобках мы указываем ключ значение, которого мы хотели бы вывести.
Смотрим на название вкладки
красота! =)
вообще у нас сейчас много копипасты в index.html, давайте немного отрефакторим наш код:
// ...
$url = $_SERVER["REQUEST_URI"];
// добавил две переменные
$title = "";
$template = "";
// тут теперь просто заполняю значение переменных
if ($url == "/") {
$title = "Главная";
$template = "main.twig";
} elseif (preg_match("#/mermaid#", $url)) {
$title = "Русалка";
$template = "mermaid.twig";
} elseif (preg_match("#/uranus#", $url)) {
$title = "Уран";
$template = "uranus.twig";
}
// рендеринг делаем один раз по заполненным переменным
echo $twig->render($template, [
"title" => $title
]);
Обобщаем страницы с картинками
Давайте теперь поглядим на страницы с картинками, они в принципе ничем не отличаются, кроме как адресом картинки. А раз так, давайте сделаем один шаблон под картинки:
вопрос только что в адресе писать?
Вообще, можно завести переменную под картинку, вот так:
$title = "";
$template = "";
$image = ""; // добавил переменную
if ($url == "/") {
$title = "Главная";
$template = "main.twig";
} elseif (preg_match("#/mermaid#", $url)) {
$title = "Русалка";
$template = "base_image.twig"; // используем шаблон base_image теперь
$image = "/images/mermaid.jpg"; // заполняю
} elseif (preg_match("#/uranus#", $url)) {
$title = "Уран";
$template = "base_image.twig"; // и тут тоже base_image
$image = "/images/uranus.png"; // и здесь заполняю
}
echo $twig->render($template, [
"title" => $title,
"image" => $image, // передаю
]);
теперь можно в шаблоне base_image.twig
подцепить:
{% extends "__layout.twig" %}
{% block content %}
<img src="{{ image }}" style="width: 300px;"/>
{% endblock %}
тестируем:
можно удалить отдельные шаблоны под русалку и уран:
вроде все ок, единственное у нас получается, что image передается и на главную страницу хотя там не нужен. Это не очень грамотно с точки зрения архитектуры.
Поэтому сделаем немного по-другому. Мы создадим пустой словарик который будет заполнятся нужными ключ-значениями по необходимости, вот так:
// ...
$url = $_SERVER["REQUEST_URI"];
$title = "";
$template = "";
// $image = ""; убираем
$context = []; // наш словарик, данные для шаблона принято называть контекстом
if ($url == "/") {
$title = "Главная";
$template = "main.twig";
} elseif (preg_match("#/mermaid#", $url)) {
$title = "Русалка";
$template = "base_image.twig";
$context['image'] = "/images/mermaid.jpg"; // передаем в контекст ключ image
} elseif (preg_match("#/uranus#", $url)) {
$title = "Уран";
$template = "base_image.twig";
$context['image'] = "/images/uranus.png"; // и тут передаем в контекст ключ image
}
// название не пихаю в контекст в роутере,
// потому что это отдельная сущность, общая для всех
$context['title'] = $title;
// ну и рендерю
echo $twig->render($template, $context);
Ну не прекрасно ли! =)
Как делать задание 5
Добавление активного класса
Чтобы вставить активный класс в Twig, надо использовать условный оператор.
Например, я хочу, чтобы на главной страницы был добавлен класс “active”. Делается это так:
<div>
<a href="/" class="{% if title == 'Главная' %}active{% endif %}">Главная</a>
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
</div>
выглядит немного проще чем на чистом php, но все равно громоздко.
К счастью в twig эту конструкцию можно записать в упрощённом виде с использование тернарного оператора, вот так:
<div>
<a href="/" class="{{ title == 'Главная' ? 'active' : '' }}">Главная</a>
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
</div>
так как это однострочная операция тут используются двойные фигурные скобки {{ ... }}
А на самом деле есть еще более простая форма записи, вот так:
<div>
<a href="/" class="{{ title == 'Главная' ? 'active' }}">Главная</a>
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
</div>
то есть, если вы после двоеточия выдаете пустую строку, то прописывать это не обязательно.
Формируем навигацию динамически
Сейчас у нас навигация делается вручную, то есть мы прописываем каждую ссылку прямо в разметке. Это нормально, когда пунктов меню мало, но создает проблемы, когда их становится много и еще если каждый надо стилизовать как-нибудь.
Для этих целей в twig встроена возможность создавать элементы в цикле. То есть у вас условно есть список элементов меню, а в шаблоне вы просто проходитесь по этому списку и динамически создаете элементы. И даже если у вас 100 пунктов меню вы разметку прописываете только для одного.
Давайте сделаем список под меню в index.php, вот так:
// ...
$title = "";
$template = "";
$context = [];
$menu = [ // добавил список словариков
[
"title" => "Главная",
"url" => "/",
],
[
"title" => "Русалка",
"url" => "/mermaid",
],
[
"title" => "Уран",
"url" => "/uranus",
]
];
// ...
теперь запихаем это в контекст:
// ...
$context['title'] = $title;
$context['menu'] = $menu; // передаем меню в контекст
echo $twig->render($template, $context);
и обновим шаблон __layout.twig
чтобы он создавал меню на основании этого списка:
<body>
<!-- ЭТО УБИРАЕМ
<div>
<a href="/">Главная</a>
<a href="/mermaid">Русалка</a>
<a href="/uranus">Уран</a>
</div>-->
<div> <!-- добавляем цикл по всем элементам меню -->
{% for item in menu %}
<!--так как item это словарик с двумя ключами, используем их для вывода ссылки -->
<a href="{{item.url}}">{{item.title}}</a>
{% endfor %}
</div>
{% block content %}
пустота
{% endblock %}
</body>
плюс можно и на активный элемент тут сразу проверять:
<div>
{% for item in menu %}
<a href="{{item.url}}" class="{{ title == item.title ? 'active' }}">{{item.title}}</a>
{% endfor %}
</div>
добавим какой-нибудь стиль для активной ссылки, чтобы лучше было видно:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
<style>
/* добавил стиль */
a.active {
background-color: yellow;
}
</style>
</head>
<body>
<div>
{% for item in menu %}
<a href="{{item.url}}" class="{{ title == item.title ? 'active' }}">{{item.title}}</a>
{% endfor %}
</div>
<!-- ... -->
</body>
</html>
и протестим:
работает! =)
Многоуровневое наследование шаблонов
Согласно заданию у вас есть главный шаблон, а есть еще шаблон объекта, который наследует главный шаблон. И этот шаблон объекта должен наследоваться шаблонами, уточняющими отображение объекта шаблона.
И допустим я хочу, чтобы для объектов писалась какая-нибудь фраза которая использует $title
ну как-то так:
давайте попробуем наследовать шаблон base_image от __object
ну типа чтобы у нас надпись была сверху и плюс еще картинка. Попробуем сначала так:
{% extends "__object.twig" %}
{% block content %}
<img src="{{ image }}" style="width: 300px;"/>
{% endblock %}
смотрим:
хм, почему-то ничего не поменялось…
В чем же дело?
Дело в том что наш шаблон base_image переопределяет содержимое блока {% block content %}{% endblock %}, и из-за этого фраза, которая там присутствует в шаблоне __object
теряется.
Поэтому, когда вы делаете двухуровненвое наследование надо внутри нового шаблона, в нашем случае __object
, определять новый блок внутри блока content, с другим именем. Вот так:
{% extends "__layout.twig" %}
{% block content %}
<div>
{{title}} -- чудо природы
</div>
{% block objectContent %}
{% endblock %}
{% endblock %}
в качестве имени objectContent
можно использовать что угодно, я вот решил так назвать, вы можете по-другому. Но суть в том, что теперь в base_image.twig
вы должны переопределять содержимое не {% block content %}{% endblock %}
а {% block objectContent %}{% endblock %}
, следующим образом
{% extends "__object.twig" %}
{% block objectContent %}<!-- поменял тут content на objectContent -->
<img src="{{ image }}" style="width: 300px;"/>
{% endblock %}
смотрим:
то что надо)
Понятно, что в данном примере такая иерархия избыточна. Но если у вас у того же Урана есть какой-то набор своих пунктов меню, это именно то что вам надо.
Что примерно должно получится
Ну что-то такое:
Как делать задание 6
Как я уже говорил, практически все современные веб приложения работают на базе архитектуры MVC, у нас из нее сейчас только более менее выделился пункт V то бишь View – представление, ну или шаблон если более понятно.
Если вы писали код примерно, как я, то у вас должна была бы получится двухуровневая структура в роутере
как вариант вы могли обойтись без вложенных if-ов но тогда вам бы приходилось дублировать код, ответственный за название галактики, общее описание объекта и картинку
в общем оба подхода одинаково плохи. Их неудобно читать и не приятно писать.
Мы же возьмем лучшее от этих двух подходов. Плоскую структуру из второго и иерархию из первого. Для этого мы воспользуемся возможности создавать классы в php.
Классы позволят нам утащить код из if-ов а также строить иерархию наследования. Ну типа у общей страницы андромеды название “Галактика Андромеды”, а у страница с картинкой то же название, но плюс там еще появляется информация о картинки.
Создаем контроллеры
И так, создаем папку controllers и в ней файл BaseController
Пишем в него
<?php
// класс абстрактный, чтобы нельзя было создать экземпляр
abstract class BaseController {
// так как все вертится вокруг данных, то заведем функцию,
// которая будет возвращать контекст с данными
public function getContext(): array {
return []; // по умолчанию пустой контекст
}
// с помощью функции get будет вызывать непосредственно рендеринг
// так как рендерить необязательно twig шаблоны, а можно, например, всякий json
// то метод сделаем абстрактным, ну типа кто наследуем BaseController
// тот обязан переопределить этот метод
abstract public function get();
}
Создаем еще один класс и назовем его TwigBaseController
<?php
require_once "BaseController.php"; // обязательно импортим BaseController
class TwigBaseController extends BaseController {
public $title = ""; // название страницы
public $template = ""; // шаблон страницы
protected \Twig\Environment $twig; // ссылка на экземпляр twig, для рендернига
// теперь пишем конструктор,
// передаем в него один параметр
// собственно ссылка на экземпляр twig
// это кстати Dependency Injection называется
// это лучше чем создавать глобальный объект $twig
// и быстрее чем создавать персональный $twig обработчик для каждого класс
public function __construct($twig)
{
$this->twig = $twig; // пробрасываем его внутрь
}
// переопределяем функцию контекста
public function getContext() : array
{
$context = parent::getContext(); // вызываем родительский метод
$context['title'] = $this->title; // добавляем title в контекст
return $context;
}
// функция гет, рендерит результат используя $template в качестве шаблона
// и вызывает функцию getContext для формирования словаря контекста
public function get() {
echo $this->twig->render($this->template, $this->getContext());
}
}
И вероятно, может возникнуть вопрос, а нафига столько кода, какие-то абстракции, и все такое?
А мы сейчас используя эти базовые классы создадим контроллеры под все наши виды и причем код в роутере у нас станет в разы меньше, а читать и поддерживать все станет многократно проще.
Добавляем контроллер главной страницы
Создаем контроллер MainController под main страницу
и пишем в нем… а почти ничего и не пишем =О
<?php
require_once "TwigBaseController.php"; // импортим TwigBaseController
class MainController extends TwigBaseController {
public $template = "main.twig";
public $title = "Главная";
}
далее идем и правим в роутере:
<?php
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php"; // добавим в самом верху ссылку на наш контроллер
// ...
$context = [];
$controller = null; // создаем переменную под контроллер
if ($url == "/") {
$controller = new MainController($twig); // создаем экземпляр контроллера для главной страницы
} elseif (preg_match("#^/andromeda#", $url)) {
// пока не трогаем ...
} elseif (preg_match("#^/orion#", $url)) {
// и это тоже ...
}
/* УБИРАЕМ
$context['title'] = $title;
echo $twig->render($template, $context);
*/
// проверяем если controller не пустой, то рендерим страницу
if ($controller) {
$controller->get();
}
тестим:
работает так же, но в самом роутере, то есть внутри if-а всего одна строчка – создание экземпляра контроллера. Что можно считать своего рода достижением!)
Но мизерным, там ведь и раньше особо ничего не было…
Добавляем контроллер страницы Андромеды
Теперь попробуем создать контроллер AndromedaController
под вывод инфы о андромеде.
Делаем по аналогии, по сути копипастим то, что было в роутере.
То есть указали title, указали шаблон, в getContext() указали специфичные для страницы ключи/значения
теперь идем обратно в index.php
и там пишем:
<?php
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php";
require_once "../controllers/AndromedaController.php"; // не забываем добавить импорт
// ...
$controller = null;
if ($url == "/") {
$controller = new MainController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {
$controller = new AndromedaController($twig); // тут просто контроллер создаем
// image и info пока не трогаем
if (preg_match("#^/andromeda/image#", $url)) {
// ...
} elseif (preg_match("#^/andromeda/info#", $url)) {
// ...
}
} elseif (preg_match("#^/orion#", $url)) {
// ...
}
// ...
проверяем:
то есть работает так же! =О
Добавляем контроллер страницы изображения Андромеды
Сейчас самое интересное. У нас шаблон страницы Андромеды с картинкой, это по сути базовая страница с Андромедой, плюс дополнительные свойства.
Ну то есть в рамках twig
у нас уже есть двухуровневая иерархия. Теперь такую же иерархию мы делаем на уровне контроллера, то есть создаем контроллер, который наследует уже не TwigBaseController
, а AndromedaController
. Оот так:
опять подправляем index.php
<?php
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php";
require_once "../controllers/AndromedaController.php";
require_once "../controllers/AndromedaImageController.php"; // добавил
// ...
$controller = null;
if ($url == "/") {
$controller = new MainController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {
$controller = new AndromedaController($twig);
if (preg_match("#^/andromeda/image#", $url)) {
$controller = new AndromedaImageController($twig); // теперь тут AndromedaImageController
} elseif (preg_match("#^/andromeda/info#", $url)) {
// ...
}
} elseif (preg_match("#^/orion#", $url)) {
// ...
}
// ...
тестим:
работает! =)
Добавляем контроллер страницы информации об Андромеде
по аналогии делаем AndromedaInfoController
<?php
require_once "AndromedaController.php";
class AndromedaInfoController extends AndromedaController {
public $template = "andromeda_info.twig";
public function getContext(): array
{
$context = parent::getContext();
// ...
return $context;
}
}
и подключаем в index.php
:
<?php
require_once "../vendor/autoload.php";
require_once "../controllers/MainController.php";
require_once "../controllers/AndromedaController.php";
require_once "../controllers/AndromedaImageController.php";
require_once "../controllers/AndromedaInfoController.php"; // добавил
// ...
$controller = null;
if ($url == "/") {
$controller = new MainController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {
$controller = new AndromedaController($twig);
if (preg_match("#^/andromeda/image#", $url)) {
$controller = new AndromedaImageController($twig);
} elseif (preg_match("#^/andromeda/info#", $url)) {
$controller = new AndromedaInfoController($twig); // теперь и тут только AndromedaInfoController
}
} elseif (preg_match("#^/orion#", $url)) {
// ...
}
// ...
И вот, если посмотреть, то вложенный if нам для андромеды теперь не нужен!
И мы вполне можем сделать структуру роутера плоской, по крайне мере для андромеды, вот так:
if ($url == "/") {
$controller = new MainController($twig);
} elseif (preg_match("#^/andromeda/image#", $url)) {
$controller = new AndromedaImageController($twig);
} elseif (preg_match("#^/andromeda/info#", $url)) {
$controller = new AndromedaInfoController($twig);
} elseif (preg_match("#^/andromeda#", $url)) {
$controller = new AndromedaController($twig);
} elseif (preg_match("#^/orion#", $url)) {
// ...
}
Единственное, обратите внимание что я поменял порядок проверки на совпадение с url, так чтобы более точный шаблоны (с image или c info) шел раньше, чем более общий (типа #^/andromeda#)
Ну для второго объекта я думаю вы сами сделаете. Но прежде чем начнете,
Создаем 404 страницу
давайте еще сделаем 404 страницу, то есть это страница, когда ссылка на страницу не существует (ну в нашем случае если, например, контроллер не определен).
Создадим контроллер и называем его Controller404.php
и пишем в него простой код:
<?php
require_once "TwigBaseController.php";
class Controller404 extends TwigBaseController {
public $template = "404.twig";
public $title = "Страница не найдена";
}
я там прописал шаблон 404.twig
давайте его создадим в папке views
:
и загоним в него что-то такое
{% extends "__layout.twig" %}
{% block content %}
<div class="text-center">
<img src="/images/025_picture-47918.gif" alt="">
<div>
Страница не найдена! Шо делать!!!111 =О
</div>
</div>
{% endblock %}
так, и подключим в index.php
<?php
// ...
require_once "../controllers/AndromedaInfoController.php";
require_once "../controllers/Controller404.php"; // добавил
// ...
$context = [];
$controller = new Controller404($twig); // теперь 404 будут нашем контроллером по умолчанию
if ($url == "/") {
// ...
так как для Ориона я еще контроллеры не делал, то по идее тыкнув на страницу Ориона, я как раз должен вызвать этот контроллер. Пробуем:
тут еще один момент, у нас 404 страница не совсем настоящая получается. И не потому что я не написал на ней 404, а потому что код ответа, который приходит вместе с этой страницей не 404, а 200.
Код ответа можно посмотреть следующим образом. Нажмите в браузере F12
или Ctrl+Shift+I
найдите вкладку Network (или Сеть), и там найдите самый первый запрос:
вот то что в столбце Status – это и есть код возврата страницы. Чтобы сделать его настоящим 404, надо добавить в контроллер вызов специальной функции http_response_code:
<?php
require_once "TwigBaseController.php";
class Controller404 extends TwigBaseController {
public $template = "404.twig";
public $title = "Страница не найдена";
public function get()
{
http_response_code(404); // с помощью http_response_code устанавливаем код возврата 404
parent::get(); // вызываем базовый метод get(), который собственно уже отрендерит страницу
}
}
запустим еще раз, и снова глянем что в столбце статус:
вот теперь другое дело!)