Урок 7. Файлы и подключение: include и require
У сайта много страниц, но шапка, меню и подвал на них одинаковые. Копировать их в каждый файл — плохая идея: правка меню превратится в правку двадцати файлов. В PHP общие части выносят в отдельные файлы и подключают командой require.
Сайт из частей
Файл header.php — начало каждой страницы:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title><?= htmlspecialchars($title) ?></title>
</head>
<body>
<nav><a href="/">Главная</a> · <a href="/about.php">О сайте</a></nav>Файл footer.php — конец:
<footer>© <?= date('Y') ?></footer>
</body>
</html>Страница index.php задаёт заголовок и подключает обе части:
<?php
$title = 'Главная';
require __DIR__ . '/header.php';
?>
<h1>Добро пожаловать</h1>
<p>Текст главной страницы.</p>
<?php require __DIR__ . '/footer.php'; ?>Подключённый файл выполняется так, будто его текст вставлен на место require. Поэтому header.php видит переменную $title из index.php. Страница about.php устроена так же, только со своим заголовком и текстом.
include, require и _once
| Команда | Если файла нет | Если файл уже подключён |
|---|---|---|
include | предупреждение, программа идёт дальше | подключит ещё раз |
require | фатальная ошибка, программа остановится | подключит ещё раз |
include_once | предупреждение | не подключит повторно |
require_once | фатальная ошибка | не подключит повторно |
Почти всегда нужен require: без шапки или настроек страница всё равно не заработает, и лучше сразу увидеть ошибку. Файлы с функциями и классами подключают через require_once — повторное объявление функции вызвало бы ошибку Cannot redeclare.
Зачем __DIR__
__DIR__ — встроенная константа: папка, где лежит текущий файл. Путь от __DIR__ одинаково работает отовсюду:
<?php
require __DIR__ . '/inc/functions.php';
require __DIR__ . '/../config.php';Вторая строка подключает файл из папки уровнем выше.
Без __DIR__ путь легко сломать. Путь вроде require 'inc/menu.php' PHP ищет не от папки файла, а по списку папок include_path и от текущей рабочей папки. Пути с ./ и ../ — только от рабочей папки. Рабочая папка — та, из которой запущен скрипт, и она не всегда совпадает с папкой файла. Особенно часто путаница случается, когда файлы подключают друг друга из разных папок.
На Linux-сервере в именах файлов важен регистр: Header.php и header.php — разные файлы. В Windows они совпадают, поэтому ошибка часто всплывает только после выкладки сайта на хостинг.
Файл настроек
Подключённый файл может вернуть значение через return. Так удобно хранить настройки:
<?php
return [
'db_host' => 'localhost',
'db_name' => 'shop',
'per_page' => 20,
];Если это файл config.php, настройки читаются одной строкой:
<?php
$config = require __DIR__ . '/config.php';
echo $config['per_page'];Файлы с паролями держите вне папки, которую видно из интернета, и не добавляйте их в публичные репозитории.
Чтение и запись файлов
Простые операции делаются одной функцией:
<?php
$file = __DIR__ . '/notes.txt';
file_put_contents($file, "Первая строка\n");
file_put_contents($file, "Вторая строка\n", FILE_APPEND);
echo file_get_contents($file);
$lines = file($file, FILE_IGNORE_NEW_LINES);
echo count($lines) . "\n";Первая строка
Вторая строка
2file_put_contentsзаписывает строку в файл и создаёт его, если файла нет. Без флага файл перезаписывается, сFILE_APPEND— дописывается в конец.file_get_contentsчитает весь файл в строку.fileчитает файл в массив строк;FILE_IGNORE_NEW_LINESубирает переводы строк в конце каждой.
Если в файл одновременно пишут несколько посетителей, добавьте блокировку: FILE_APPEND | LOCK_EX. Тогда записи не перемешаются.
Большой файл (журнал на сотни мегабайт) целиком в память не читают, а идут по строкам:
<?php
$handle = fopen(__DIR__ . '/notes.txt', 'r');
if ($handle === false) {
exit("Файл не открылся\n");
}
while (($line = fgets($handle)) !== false) {
echo mb_strtoupper(trim($line)) . "\n";
}
fclose($handle);fopen открывает файл ('r' — для чтения, 'w' — для записи с очисткой, 'a' — для дописывания), fgets читает очередную строку и возвращает false в конце файла, fclose закрывает файл.
Проверить, есть ли файл, — file_exists($path) или is_file($path); удалить — unlink($path); создать папку — mkdir($path).
Права на сервере
На Linux-сервере PHP работает от имени пользователя веб-сервера (в Ubuntu и Debian это обычно www-data). Если у него нет права писать в папку, запись закончится предупреждением Failed to open stream: Permission denied. Дайте право на запись только той папке, куда сайт действительно пишет (например, data или uploads), а не всему сайту.
Опасно: имя файла из адреса
Никогда не подключайте файл по имени, пришедшему от посетителя: include $_GET['page'];. Подставив в адрес ../ и другие пути, злоумышленник заставит сайт выполнить чужой PHP-файл или показать любой файл сервера, который может прочитать PHP, — например, настройки с паролями.
Безопасный способ — список разрешённых страниц:
<?php
$pages = [
'home' => 'home.php',
'about' => 'about.php',
'contacts' => 'contacts.php',
];
$page = $_GET['page'] ?? 'home';
if (!isset($pages[$page])) {
http_response_code(404);
$page = 'home';
}
require __DIR__ . '/pages/' . $pages[$page];Посетитель выбирает только ключ, а имя файла берётся из списка, который составили вы.
Частые ошибки
Failed to open stream: No such file or directoryуrequire— неверный путь: он записан без__DIR__, в имени опечатка или другой регистр букв. Подробно — справка Как подключить файл.Cannot redeclare— файл с функциями подключён дважды. Подключайте его черезrequire_once.Permission deniedпри записи — у PHP нет права писать в папку (раздел «Права на сервере» выше).- Ошибка
headers already sentпосле подключения файла — в нём пустая строка или пробел до<?phpили после?>: этот текст ушёл в браузер раньше заголовков (справка headers already sent).
Коротко
- Общие части сайта выносят в файлы и подключают
require __DIR__ . '/файл.php'. requireпри ошибке останавливает программу,include— только предупреждает;_onceне подключает файл дважды.__DIR__— папка текущего файла: путь от неё работает из любого места.file_put_contentsпишет (сFILE_APPEND— дописывает),file_get_contentsчитает,file— в массив строк.- Имя подключаемого файла из адреса или формы — только через список разрешённых.