Бэкенд / Создание простого многостраничного сайта

Начнем с истории. В стародавние времена, когда 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 файла

Такие дела *______*

3

Разбираемся с обработкой простых веб-запросов и делаем простой многостраничный сайт.

Прежде чем начать делать это задание вам надо придумать себе тему про что вы будете делать сайт.

Это могут быть персонажи, или фильмы, или какие-то музыкальные произведения, инструменты, автомобили, деревья, цветы в общем что вам любо. Главное, чтобы этому объекту можно было сопоставить картинку и какое-нибудь описание. Пока вам хватит двух экземпляров вашей темы.

Я выбрал тему космических туманностей. У меня в качестве подопытных: Галактика Андромеда и Туманность Ориона