ДЛЯ ТЕХ КТО НА WINDOWS / Тут настраиваем laragon для удобной разработки на PHP
Бэкенд
ДЛЯ ЛИНУКСОИДОВ и МАКОВ / Настройка php для локальной разработки (как настроить на проде либо если хочется поковыряться в nginx, то смотри модуль по vps)
Разбираемся с обработкой простых веб-запросов и делаем простой многостраничный сайт.
Прежде чем начать делать это задание вам надо придумать себе тему про что вы будете делать сайт.
Это могут быть персонажи, или фильмы, или какие-то музыкальные произведения, инструменты, автомобили, деревья, цветы в общем что вам любо. Главное, чтобы этому объекту можно было сопоставить картинку и какое-нибудь описание. Пока вам хватит двух экземпляров вашей темы.
Я выбрал тему космических туманностей. У меня в качестве подопытных: Галактика Андромеда и Туманность Ориона
Доработать сайт:
- Добавить дополнительные страницы с информацией об объекте и с картинкой объекта
- Под новые страницы сделать отдельные файлы, которые будут подключаться со страницы объекта
- На главную страницу вывести ссылки на все страницы
- На дополнительных страницах должно быть видно какой пункт меню выбран
Должно получится что-то такое:
Подключаем шаблонизатор 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
Как делать задание 1
И так, для того чтобы понять, как работает веб будем использовать PHP. Будучи своего рода первопроходцов в становлении современного веба, используя его будет проще всего проследить то почему веб стал таким каким он стал ну и в плане перехода от верстки, к верстки с элементами программирования а затем и к полноценнному веб приложения с MVC и прочему это прям идеальный вариант.
Для работы с php будем использовать laragon. Это специальная программка, которая позволяет разворачивать веб приложение из любой папки, без необходимости наличия прав администратора, а также дает возможность ставить всякие сервисы типа mysql, nginx, php и т.п. Можно таскать на флешке. И в отличии от виртуальной машины оно работает очень быстро и занимает совсем немного место.
И так идем на сайт https://laragon.org/download/ и качаем портабельную версию:
распаковываем в папку
получится такое:
теперь скачаем PHP. Идем сюда https://windows.php.net/download#php-8.1 и качаем:
сохраняем внутрь папке laragon-portable в подпапках \bin\php
:
и тоже распаковываем
ну и еще зайдите в настройки laragon и снимите галочку
собственно, всё. Мы файлы нужные собрали и можно уже попробовать позапускать сервер.
Запускаем файлик laragon.exe
после запуска увидим такой интерфейс
нажимаем кнопку 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
Для debian-based
Чтобы разворачивать php проекты, необходимо установить собственно сам php. К сожалению, в убунте по умолчанию доступна немного устаревшая 7-ая версия php.
Поэтому чтобы не отставать от прогресса, подключим сторонний репозиторий, который содержит самые новые версии php. Репозиторий поддерживает Ондржей Сури, разработчик Debian, так что источник надежный.
В общем пишем
sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
первая команда ставит пакет, который упрощает подключение сторонних репозиториев, а вторая собственно подключает репозиторий.
На вопрос тыкаем enter
после того как репозиторий добавится и обновится кэш можно ставить себе php. Пишем (вместо 8.2 можно написать другую версию)
sudo apt install php8.2-fpm -y
И ждем пока установится.
php-fpm – это не просто компилятор/интерпретатор php, это целый сервис, который можно подключить к nginx и, который будет автоматом выполнять скрипты и возвращать результат их обработки обратно юзеру.
Для маков
Я не тестил, но по идее достаточно выполнить команду
brew install php
вот еще полезная ссылочка
Настраиваем процесс разработки
В принципе можно все развернуть как для VPS, но нам пока достаточно настроить php для локальной разработки. Проверяем что php установился нормально:
Ставим в visual studio code плагин для php
теперь можно завести папку с проектом, создать в ней файлик index.php
загнать в него
<?php echo phpinfo(); ?>
открыть терминал и написать в нем
php -S localhost:3000
вот так
теперь открываем в бразуере http://localhost:3000
и пожалуйста:
можно разрабатывать =)
Как делать задание 3
Начнем с истории. В стародавние времена, когда 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.2/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.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
</head>
<body>
<?php print_r($_SERVER["REQUEST_URI"]) ?>
</body>
</html>
теперь нам надо вставить меню навигации, опять же его можно скопипастить с сайта bootstrap, идем сюда https://getbootstrap.com/docs/5.2/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/6.1.2/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 у каждого пункта меню. Прописываем:
попробуем теперь потыкать:
класс! =)
То есть формально мы вроде страницы не переключаем, но выглядит будто бы мы таки переключаем. Пусть вас это не смущает, так работают 99% сайтов в мире
Управляем содержимым
Давайте теперь попробуем чего-нибудь вывести, например, на главной страницу будет список ссылок на все страницы (ну типа продублируем навигацию), а на страницах туманностей будут картинки
Способ №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 нам выдаст
Для Windows
теперь надо настроить laragon чтобы он в качестве root папки использовал папку public, идем в laragon,
выбираем папку public
и вроде как и всё, но проблема в том что laragon сбросил наши настройки nginx, давайте зайдем туда и глянем:
смотрим
и так, чтобы эти настройки не терялись, нам надо подправить шаблон laragon по которому он пересоздает файлик 00-default.conf
когда вы переключаете папку. Идем в папку куда ставили laragon и заходим там в /usr/tpl
открываем файлик nginx.sites-enabled.00-default.manifest.tpl
и правим ему location, чтобы был как в прошлый раз мы настраивали:
server {
...
location / {
try_files $uri $uri/ /index.php$is_args$args; # добавил
# а это закоментил или можно вообще удалить
#try_files $uri $uri/ =404;
#autoindex on;
}
...
}
теперь еще раз переключаем на php_01
и обратно на php_01\public
На линуксе
Просто запустите локальный сервер указав папку public
php -S localhost:3000 -t public
Тестируем
Проверяем:
хохо! Работает! Только ошибки сыпятся. Но оно и понятно у нас ведь в 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 файла
Такие дела *______*
Как делать задание 4
Проверка строки на частичное соответствие
Кстати вам скорее всего потребуется проверять проверка совпадения строки не на полное соответствие, а, например, что какая-та строка просто совпадает частично, например, так мы можем проверить что url начинается со слова андромеда
if (preg_match("#^/andromeda#", $url)) {
require "../views/andromeda.php";
}
Тут мы используем регулярное выражение. Вот так может понятнее:
Создание иерархии файлов
Организовывая файлы в проекте, надо придерживаться следующего подхода:
1) index.php – это точка входа вашего веб-приложения
2) только в файле index.php содержится теги html, body, шапка с навигацией
3) файл index.php разбит на две основные области
в свою очередь каждый файлик andromeda или orion должен быть ответственен только за свою разметку и иметь примерно следующую структуру
причем вместо того чтобы пихать и описание, и картинку в один файл как в примере выше, лучше вынести их в отдельные файлы. Чтобы, например, файл andromeda решал на основании переменных is_image, is_info, какой файл подключать через require (прямо как в index.php). В результате должна получиться примерно такая структура файлов:
Подсветка активного элемента
Скорее всего возникнет вопрос, как реализовать подсветку активного элемента, ну чтобы работало как тут:
во-первых, тут используется элемент бутстрап https://getbootstrap.com/docs/5.2/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>
тут используется специальный упрощенный блок <?= ... ?>
, который как раз представляет из себя по сути echo инструкции внутри. То есть, если в него можно запихать какое-нибудь выражение и он сработает как
<?php echo $is_image ? "active" : '' ?>
Элементы на главной страницы
С элементами главной страницы думаю вопросов особо не возникнет. Я использовал https://getbootstrap.com/docs/5.2/components/list-group/ для списка
а для названий страницы кнопочки https://getbootstrap.com/docs/5.2/components/buttons/
ну и чтобы все не слиплось использовал классы для отступов https://getbootstrap.com/docs/5.2/utilities/spacing/#margin-and-padding
Как делать задание 5
Идем дальше по дорожке развития 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, которая идет с laragon, устаревшая, так что придется ее обновить. Идем на сайт https://getcomposer.org/download/, скролим вниз и качаем последнюю версию и сохраняем в папку laragon-portable\bin\composer
после того как скачали, проверяем в консольке laragon версию
composer -V
должно выдать более менее актуальную дату
Ну теперь можно попробовать запустить команду установки
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.
Рендерим с помощью 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);
Ну не прекрасно ли! =)
Как делать задание 6
Так давайте еще немного потерзаем наш второй проект.
Добавление активного класса
Помните вам в 3-ем задании надо было подсвечивать активный пункт в меню и там мы использовали <?= $is_image ? "active" : '' ?>
.
Теперь посмотрим как это делать в 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 %}
смотрим:
то что надо)
Понятно, что в данном примере такая иерархия избыточна. Но если у вас у того же Урана есть какой-то набор своих пунктов меню, это именно то что вам надо.
Что примерно должно получится
Можно теперь вернуться к основному проекту. Возникнет сразу вопрос, как туда все подключать и как там писать код. Должно получится что-то такое:
Как делать задание 7
Как я уже говорил, практически все современные веб приложения работают на базе архитектуры 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(), который собственно уже отрендерит страницу
}
}
запустим еще раз, и снова глянем что в столбце статус:
вот теперь другое дело!)