PHP / [Перевод] Автозагрузка в PHP: начали за здравие, а кончили за упокой

в 8:43, , рубрики: autoload, php, spl_autoload

Предисловие переводчика

Данная статья является вольным переводом-пересказом поста The End of Autoloading
. Оригинальная статья не первой свежести, поэтому код приведенный в примерах может быть не актуален. В теме, которую затрагивает статья, самое главное — общий взгляд, а не конкретные примеры.

Предисловие

Автозагрузка в PHP отлично экономит время. Это позволяет писать скрипты не задумываясь о путях к библиотекам, которые вы используете. Но с приходом неймспейсов и под влиянием Java-стиля современных фреймворков ситуация изменилась. В ближайшем будущем автозагрузка будет повсеместно, но без единой выгоды старого ее стиля.

До автозагрузки. Пути к файлам.

До появления автозагрузки, в каждом скрипте необходимо было указывать пути к используемым библиотекам. Исходный код выглядел так:

<?php require_once 'PEAR.php'; require_once 'PEAR/DependencyDB.php';  class PEAR_Registry extends PEAR {   //... } 

Зависимости явно прописывались в начале каждого скрипта. В этом примере зависимость очевидна, даже если вызов PEAR_DependencyDB находится на 328 строке.

Приход SPL-автозагрузки.

Позже появилась функция spl_autoload_register(), позволившая убрать вызовы require/require_once. Замечательным было то, что теперь появилась возможность использовать классы без необходимости знания где они расположены. Например:

<?php class postActions extends sfActions {   public function executeList()   {     $this->posts = PostPeer::doSelect(new Criteria());   } } 

Смотрите, здесь нет ни единого вызова require/require_once, при том, что этот класс зависим от sfActions, PostPeer, и Criteria классов. Разработчики смогли заниматься бизнес-логикой, не тратя время на поиск путей зависимостей. Это было действительно удобно.

Реализации автозагрузки.

Реализации автозагрузки варьируются. Некоторые библиотеки используют список всех классов, которые нужно подключить. Например:

<?php class Propel {   // ..   protected static $autoloadMap = array(     'DBAdapter'      => 'adapter/DBAdapter.php',     'DBMSSQL'        => 'adapter/DBMSSQL.php',     'MssqlPropelPDO' => 'adapter/MSSQL/MssqlPropelPDO.php',     'MssqlDebugPDO'  => 'adapter/MSSQL/MssqlDebugPDO.php',     // etc. } 

Эта техника позволила скрыть пути к классам, но заставила разработчика библиотеки обновлять “карту” автозагрузки, каждый раз, при добавлении нового класса. Другая техника использует проход по структуре папок проекта в поиске нужного класса. Такой подход позволил подменять классы фреймворка своими, т.к. проход по папкам происходил в определенном порядке: пользовательские папки, проекта, плагинов и фреймворка. Таким образом разработчик мог создать класс ClassName, который подменит ClassName предоставленный плагином, т.к. загрузится раньше.

Автозагрузка с неймспейсами

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

DoctrineCommonIsolatedClassLoader   => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php SymfonyCoreRequest   => /path/to/project/lib/vendor/Symfony/Core/Request.php ZendAcl   => /path/to/project/lib/vendor/Zend/Acl.php ZendMailMessage   => /path/to/project/lib/vendor/Zend/Mail/Message.php 

Были выработаны принципы именования и файловой структуры, а также реализация класса SplClassLoader. Этот подход сейчас используется практически во всех современных фреймворках. Пример кода:

<?php namespace ApplicationHelloBundleController; use SymfonyFrameworkWebBundleController;  class HelloController extends Controller {   public function indexAction($name)   {     $author = new ApplicationHelloBundleModelAuthor();     $author->setFirstName($name);     $author->save();     return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author));   } } 

Здесь все также нет require благодаря автозагрузке. Автозагрузчик ищет SymfonyFrameworkWebBundleController класс в файле Symfony/Framework/WebBundle/Controller.php.
Все хорошо, все довольны. Зависимости явные и подмена класса происходит легко:

<?php namespace ApplicationHelloBundleController; // use a custom Controller class instead of the framework's Controller use ApplicationHelloBundleToolsController;  class HelloController extends Controller {   // same code as before } 

Конец все удобствам.

Использование use в предыдущем примере вам ничего не напоминает? Это ведь очень похоже на старый добрый require/require_once, не так ли?

<?php // old style require_once 'Application/HelloBundle/Tools/Controller.php'; // new style use 'ApplicationHelloBundleToolsController'; 

Привнесенная неймспейсами многословность в первую очередь снижает легкость использования автозагрузки. Но проблема не только в том, что нужно писать больше кода, в этом вам может помочь IDE с автодополнением, а в том, что вам нужно знать полные имена нужных вам классов. Вы должны очень хорошо знать классы фреймворка, чтобы использовать их. Это шаг назад по сравнению с автозагрузкой “первого поколения”, где было достаточно знать имя класса.

Другого пути нет.

Правда было бы замечательно, использовать современные библиотеки без знания их файловой структуры? Что если бы вы могли написать контроллер так:

<?php class HelloController extends Controller {   public function indexAction($name)   {     $author = new Author();     $author->setFirstName($name);     $author->save();     return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author));   } } 

Умный автозагрузчик перехватил бы вызов класса Controller, загрузил бы файл Symfony/Framework/WebBundle/Controller.php и динамически создал алиас с SymfonyFrameworkWebBundleController на Controller. К сожалению в PHP use создает алиас во время компиляции, поэтому такой ход не сработает. Конечно есть возможность сделать подобное используя eval, но это, наверное, даже хуже, чем подключать файлы вручную. Также создание таких алиасов при работе с фреймворком не возможно по причине конфликта с ленивой загрузкой и конфликта имен классов, например:
SymfonyFrameworkWebBundleCommand
и
SymfonyComponentsConsoleCommandCommand
Пока авторы фреймворков не изменят свой взгляд на автозагрузку, будущее PHP мне видится многословным.

Решение проблемы.

Лично я, думаю, что многословность сильно замедляет разработку. Например возьмем микрофреймворки – они дают возможность обработать запрос быстро, при минимуме MVC-разделения. Сравним код примерного приложения написанного с использованием Slim (автозагрузка без неймспейсов) и Silex (автозагрузка с неймспейсами):

<?php // Hello world with Slim require_once 'slim/Slim.php'; Slim::init(); Slim::get('/hello/:name', function($name) {     Slim::render('hello.php', array('name' => $name)); }); Slim::run();  // Hello world with Silex require_once 'silex.phar'; use SymfonyComponentHttpFoundationResponse; use SymfonyComponentTemplatingEngine; use SymfonyComponentTemplatingLoaderFilesystemLoader; use SilexFramework; $framework = new Framework(array(   'GET /hello/:name' => function($name) {     $loader = new FilesystemLoader('views/%name%.php');     $view = new Engine($loader);     return new Response($view->render(       'hello',        array('name' => $name)     ));   } )); $framework->handle()->send(); 

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

Разработчики современных фреймворков объясняют, что многословность – это цена, которую мы платим за качество кода. Я не уверен, что хочу платить эту цену. Я не хочу видеть как PHP превращается в Java, где код превосходен с точки зрения Computer Science, но очень затратен в написании. Это побуждает желание к использованию других языков, где вопрос автозагрузки с неймспейсами не стоит и при этом, быстрая разработка возможна.

Возьмем например Ruby. Существует такой фреймворк как Sinatra, используя который HelloWorld-приложение становится очень лаконичным:

require 'sinatra' require 'erb' get '/hello/:name' do |name|     @name = name     erb :hello end 

Ой, смотрите, здесь же используется require! И при этом, все пишется очень быстро и легко в использовании.

Автор: truezemez


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js