Форум dkLab и Denwer
Здесь общаются Web-разработчики.
Генеральный спонсор:
Хостинг «Джино»

__autoload() с кешированием (Антон Макаренко)
Author Message
Антон Макаренко
Участник форума



Joined: 05 Feb 2004
Posts: 374
Карма: 31
   поощрить/наказать

Location: Киев

PostPosted: Sun Jun 01, 2008 4:38 am (написано за 1 минуту 43 секунды)
   Post subject: __autoload() с кешированием
Reply with quote

Функция автоматически инклудит файлы с классами, см. официальное руководство (php.net/__autoload). Кроме этого, умеет склеивать загруженные автоматически файлы и записывать их в один, а при последующих запусках скрипта — инклудить этот файл, чтобы не дергать лишний раз каждый файл с классом.

Пример кода, который следует разместить где-нибудь вначале скрипта — простой вариант:
Code (php): скопировать код в буфер обмена
require 'lib/__autoload.php';
"Full-featured"-вариант:
Code (php): скопировать код в буфер обмена
// setup include path
if (!defined (www.php.net/defined)('PATH_SEPARATOR'))
    define (www.php.net/define)('PATH_SEPARATOR', getenv (www.php.net/getenv)('COMSPEC')? ';' : ':');
ini_set (www.php.net/ini_set)('include_path', ini_get (www.php.net/ini_get)('include_path')
    . PATH_SEPARATOR . dirname (www.php.net/dirname)(__FILE__) . '/lib'
    . PATH_SEPARATOR . dirname (www.php.net/dirname)(__FILE__) . '/sys/classes'
);

// initiate autoload
define (www.php.net/define)('__AUTOLOAD_CACHE_DIR', 'cache/classes');
define (www.php.net/define)('__AUTOLOAD_MUTEX_FILE', 'cache/__is_autoload');
require 'lib/__autoload.php';
register_shutdown_function (www.php.net/register_shutdown_function)('__autoload');
Именование и пути
Именование классов и структура их папок/файлов должна соответствовать соглашениям по именованию, применяемых в Zend Framework:
Quote:
Имена классов могут содержать только буквенно-числовые символы. ... Символы нижнего подчеркивания допустимы в местах разделителей пути - имя файла "Zend/Db/Table.php" должно указывать на класс с именем "Zend_Db_Table".
...
Т.е., имя класса разрезается по подчеркиванию "_", все составные части, кроме последней, будут считаться именами папок, а последняя — именем файла.

Подразумевается, что относительные пути и их приоритетность правильным образом заданы разработчиком в include path самостоятельно. Так, если в корне вашего проекта лежит файл lib/DbSimple/Generic.php, то путь к папке <корень проекта>/lib к моменту запуска функции уже должен быть зашит в include path. Поскольку в вышеуказанном файле лежит класс с именем DbSimple_Generic, то функция запросит файл DbSimple/Generic.php с помощью require.

Это основная область применения функции. К стандарту Zend лучше привыкнуть и начать использовать, не зря его написали. Насколько я знаю, стандарт PEAR именует размещает классы по аналогичной схеме.

Можно объявить константу __AUTOLOAD_INCLUDE, которая заставит функцию использовать include вместо require.

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

Говорят (forum.dklab.ru/viewtopic.php?p=155700#155700), при определенных условиях такое склеивание может ускорить работу скриптов.

Чтобы кеширование заработало, нужно сперва определить папку, куда записывать кеш, и файл-семафор (может я неправильно назвал, поправьте если что), который будет управлять кешем — это константы __AUTOLOAD_CACHE_DIR и __AUTOLOAD_MUTEX_FILE соответственно. Причем, чтобы отключить кеширование или перезагрузить кеш заново, нужно либо удалить файл, либо поменять его время модификации соответственно. Т.е., чтобы перегрузить кеш по команде из какой-то программы, достаточно вызвать touch() этого файла.

Запись в файл кеша происходит при завершении работы скрипта. Можно, конечно, вызвать __autoload() напрямую где-нибудь в конце скрипта, но лучше сделать это автоматически, с помощью register_shutdown_function(). Такой способ позволит делать запись кеша в максимально удобный момент.

Имя файла кеша будет содержать метку времени файла-семафора. Если метка времени семафора поменялась, будет просто создан новый файл кеша с новой меткой. С каждым дописыванием, функция вставляет комментарий с меткой времени, когда файл был дописан.

Надежность и эффективность
При автозагрузке файла, происходит такой же require, как будто бы вы делали его вручную из скрипта. Любые ошибки здесь неуместны, поэтому при малейшей проблеме будет вылетать Fatal error. На первый взгляд это может показаться неудобным, но на самом деле это отличный повод сделать все ваши классы по единому строгому стандарту. То же касается путей: папка с кешем должна быть доступна для записи, либо чтобы была возможность ее создать (функция об этом позаботится сама, или опять же, бросится ошибкой).

Сам файл кеша не представляет какой-либо секретности, если ваши автоматически загружаемые файлы содержат только объявления классов. Иными словами, разработчик сам отвечает за целостность и адекватность пространства имен классов, вызовы функций, объявления констант и так далее. Желающим настроить доступ к файлам по-своему, предоставляется возможность в виде констант __AUTOLOAD_DIR_MODE и __AUTOLOAD_FILE_MODE, которые можно определить до запуска функции.

Файлы должны начинаться с открывающего тега PHP: '<?php'. Крайне нежелательно, чтобы файлы заканчивались завершающим тегом '?>', но функция умеет отрезать последний закрывающий тег.

Функция еще не тестировалась в "production"-среде. Потенциально, у нее могут быть проблемы с одновременным обращением нескольких процессов к файлу в момент записи. Я решил не добавлять блокировку файла, потому что эффект их действия мне неизвестен. Также, используется clearstatcache() - лично мне неизвестно, наносит ли это удар по производительности.

Буду рад получить отзывы по использованию функции. Всем спасибо заранее.


__autoload.zip
 Description:
Функция __autoload()
v0.2

Download
 Filename:  __autoload.zip
 Filesize:  2.1 KB
 Downloaded:  1065 Time(s)



Last edited by Антон Макаренко on Tue Sep 16, 2008 12:22 am; edited 1 time in total
Back to top
View user's profile Send private message Send e-mail
samuray
Заглянувший



Joined: 14 Apr 2008
Posts: 6
Карма: 1
   поощрить/наказать


PostPosted: Tue Jun 03, 2008 3:27 pm (спустя 2 дня 10 часов 49 минут; написано за 8 минут 15 секунд)
   Post subject: Re: __autoload() с кешированием
Reply with quote

Спасибо за труд, работает на ура.
Но, есть один минус. По Вашей схеме - в одном файле должен быть один класс. Если же их больше, то появляются баги.
Например, библиотека MDB2 ругается так :
Quote:
Cannot redeclare class MDB2_Error in /usr/share/php/pear/MDB2.php on line 977
Все потому, что в файле MDB2.php находятся описание классов MBD2, MDB2_Error и др.

UPD.
Поправлюсь. Ошибка в другом. PEER-овские require_once-ы сводят на нет весь скрипт :(
Так как глупо лезть в PEER библиотеки и убирать все вызовы require_once, было принято решение не кешировать эти библиотеки.
Есть-ли другие варианты?
Back to top
View user's profile Send private message
Константин Жинько [tIT]
Сотрудник «Лаборатории»



Joined: 12 Jun 2004
Posts: 2264
Карма: 105
   поощрить/наказать

Location: Москва

PostPosted: Sat Jun 07, 2008 10:34 pm (спустя 4 дня 7 часов 6 минут; написано за 40 секунд)
   Post subject:
Reply with quote

samuray wrote:
Есть-ли другие варианты?
повырезать все require_once)
Back to top
View user's profile Send private message
samuray
Заглянувший



Joined: 14 Apr 2008
Posts: 6
Карма: 1
   поощрить/наказать


PostPosted: Wed Jun 18, 2008 4:32 pm (спустя 10 дней 17 часов 58 минут; написано за 2 минуты 26 секунд)
   Post subject:
Reply with quote

Как-нибудь на досуге попробую модифицировать скрипт так, чтобы при кешировании сам убирал все вызовы require_once. Тогда и вырезать ничего не надо будет :)
Back to top
View user's profile Send private message
Антон Макаренко
Участник форума



Joined: 05 Feb 2004
Posts: 374
Карма: 31
   поощрить/наказать

Location: Киев

PostPosted: Thu Jun 19, 2008 1:21 am (спустя 8 часов 48 минут; написано за 1 минуту 12 секунд)
   Post subject:
Reply with quote

samuray wrote:
чтобы при кешировании сам убирал все вызовы require_once
Думаю, что смысл есть делать это лишь в общеобразовательных целях. Неужели в реально работающем проекте захочется допускать такое шаманство?
Back to top
View user's profile Send private message Send e-mail
samuray
Заглянувший



Joined: 14 Apr 2008
Posts: 6
Карма: 1
   поощрить/наказать


PostPosted: Thu Jun 19, 2008 3:47 pm (спустя 14 часов 26 минут; написано за 4 минуты 32 секунды)
   Post subject:
Reply with quote

Да, боюсь что так.
Помимо банальных конструкций require/include_once('filename'), которые вырезать несложно, нашел в PEER-овских библиотеках несколько встроенных автолоадеров с хитрой логикой.
Убирать их скриптом - чистой воды шаманство.
Back to top
View user's profile Send private message
Дмитрий Котеров
Администратор



Joined: 10 Mar 2003
Posts: 13665
Карма: 413
   поощрить/наказать


PostPosted: Wed Aug 13, 2008 11:10 am (спустя 1 месяц 23 дня 19 часов 22 минуты; написано за 1 минуту 35 секунд)
   Post subject:
Reply with quote

Можно и не убирать, а где-нибудь просто создать пустые (или почти пустые) файлы и подменить include_path так, чтобы require_once включал именно эти пустые файлы.
Я это не пробовал, просто идея.

Тогда не надо будет ни PEAR менять, ни Zend Framework.
Back to top
View user's profile Send private message Send e-mail
Антон Макаренко
Участник форума



Joined: 05 Feb 2004
Posts: 374
Карма: 31
   поощрить/наказать

Location: Киев

PostPosted: Tue Sep 16, 2008 12:26 am (спустя 1 месяц 2 дня 13 часов 16 минут; написано за 1 минуту 48 секунд)
   Post subject:
Reply with quote

Залил в шапку следующую версию.
  1. class_exists() запускается со вторым параметром false, чтобы не вызывать __autoload() рекурсивно
  2. добавлена возможность использовать include вместо require
Back to top
View user's profile Send private message Send e-mail
Валенок
Участник форума



Joined: 06 Apr 2006
Posts: 520
Карма: -3
   поощрить/наказать


PostPosted: Fri Oct 23, 2009 4:47 am (спустя 1 год 1 месяц 7 дней 4 часа 20 минут; написано за 5 минут 11 секунд)
   Post subject:
Reply with quote

Может быть, я уже устарел с этим ответом :)
Но вот как я разрешил конфликты с Zend Framework, который сам грузит Zend_Loader в Zend_Application:
Code (php): скопировать код в буфер обмена
spl_autoload_register('__autoload'); // пихаем в стек автолоадеров. если успеем ;) то опередим зенд_лоадер и будет вызываться именно эта функция!
 
А вот еще пара изменений, на сей раз внутри вашей функции:
Рраз, в сохранении всего в один фаил:
Code (php): скопировать код в буфер обмена
foreach ($loadedFiles as $key => $file) {
                       
                $paths = explode (www.php.net/explode)(PATH_SEPARATOR, get_include_path (www.php.net/get_include_path)());
                foreach ($paths as $path) {
                        if (substr (www.php.net/substr)($path, -1) == DIRECTORY_SEPARATOR) {
                                $fullpath = $path.$file;
                        } else {
                                $fullpath = $path.DIRECTORY_SEPARATOR.$file;
                        }
                        if (file_exists (www.php.net/file_exists)($fullpath)) {
                                $file = $fullpath;
                        }
                } //
                       
                $contents[$key] = @trim (www.php.net/trim)(file_get_contents (www.php.net/file_get_contents)($file, true));
                if (empty (www.php.net/empty)($contents[$key])) {
                    trigger_error (www.php.net/trigger_error)('Failed to load contents from file ' . $file, E_USER_ERROR);
                }

                $contents[$key] = str_replace (www.php.net/str_replace)('__FILE__', "'".realpath($file)."'", $contents[$key]); //

                $contents[$key] = '?>' . $contents[$key];                //
                if ('?>' === substr (www.php.net/substr)($contents[$key], -2)) {
                    $contents[$key] = substr_replace (www.php.net/substr_replace)($contents[$key], "\n", -2);
                }
            }
Два, в загрузке фаила:
Code (php): скопировать код в буфер обмена
if (!empty (www.php.net/empty)($className)) {
        $path = explode (www.php.net/explode)('_', $className);
        $filename = array_pop (www.php.net/array_pop)($path);
        $file = (empty (www.php.net/empty)($path) ? '' : implode (www.php.net/implode)('/', $path) . '/') . $filename . '.php';
               
        if (!@include($file)) return; //

        // update loaded files list
        if ($isCache) {
            $loadedFiles[] = $file;
        }
    }
Спасибо большое!
Расскажите, пожалуйста, если примете изменения или там чего-нибудь сами поменяете.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic All times are GMT + 3 Hours
Page 1 of 1    Email to a Friend.
You cannot post new topics in this forum. You cannot reply to topics in this forum. You cannot edit your posts in this forum. You cannot delete your posts in this forum. You cannot vote in polls in this forum. You cannot attach files in this forum. You can download files in this forum.
XML