Прозрачное кэширование (PHP)
22.11.2010 технологии тормозные идеи
Придумал реализацию прозрачного кэшера, начал писать класс, да вот возник затык. Может быть вы поможете? Подскажите путь.
Суть идеи
Хочу, чтобы класс кэширования можно было очень легко и просто подключить к любому уже готовому проекту. Придумал реализацию через магический метод __call(). Допустим, у нас в проекте есть такая вот строчка:
$data = file_get_contents(URL);
Для большинства задач запрашивать удалённый URL каждый раз было бы совсем неразумно, нужен кэшик. Что я предлагаю?
$cacher = new classCacher;
$data = $cacher->file_get_contents(URL);
Это было бы очень удобно, на мой взгляд. И я сделал реализацию для подстановки класса перед функциями. Но если информацию выдаёт не функция, а метод другого класса, ничего не получается. В том числе и с __get. Пример:
$data = $cacher->$class->someMethod();
$data = $cacher->class::someMethod();
Вот для таких случаев не представляю даже что делать, как быть? То есть методы классов, к сожалению, не воспринимаются как отдельные функции почему-то, они не попадают ни в __call() ни в __get(), PHP вообще в таком случае пишет Catchable fatal error: Object of class Test could not be converted to string… Беда, беда. Возможно ли реализовать мою задумку вообще? Если нет, то как лучше сделать?
Комментарии
Комментирование этой статьи закрыто.
« 530 руб/мес. за хостинг... дороговато? 35 тыс. показов за первую неделю! »
Я уже давно позабывал PHP, но вроде бы там в API была функция чтобы вызвать произвольный метод через массив из двух элементов: как пару класс-метод. Не оно?
Я вообще передавал бы в запрос кэша идешник записи (URL, whatever) и строку, которую надо eval-ить, если записи в кэше нет.
Тебе же ясно пишет – не может сконвертить в string. Для этого есть магический метод __toString. Полезная ссылка: net.tutsplus.com/tut…
По поводу такого кеширования – ужасно не удобно будет. Если я правильно понимаю – ты собрался кешировать полностью страницы. Это не правильно.
Clr, оно используется, и что? Как ты это прикрутишь к методам классов? С функциями работает — я же написал. У меня есть уже прототипчик. Работает так: если кэш к такой функции есть и не протух, функция не вызывается, сразу берём данные из кэша. Если кэша нет или протух, тогда вызывается уже нужная функция и пишется кэш.
Aktuba, да понимаю я, чего он пишет. А вот ты не понял, что я хочу, скорей всего я коряво объяснил. Хотя, вроде строчки не двусмысленные. Причём здесь страницы, вообще? И я никогда не принимаю доводы «не правильно» без аргументации.
А ещё __toString() в данном примере должен прописываться в классе объекта $class, а не $cacher, что убивает всю идею о простом и быстром подключении кэша.
Я не совсем понимаю, какого кода ты хочешь добиться в результате.
В Ruby я бы сделал что-то типа этого:
value = cache.read(recordId) do # Блок вызывается при кэш-промахе someBlaBlaBla(); # и получает/вычисляет данные
end
В PHP это, наверное, можно переписать как:
$value = $cache->read(recordId, ‘someBlaBlaBla()’);
Я хочу, чтобы внедрение кэшера в уже написанные проекты было элементарным: простой подстановкой объекта кэша перед вызываемой функцией, которую надо закэшировать.
Вот в примере из заметки с file_get_contents() понятно? Это работает. А если вместо file_get_contents в коде вызов метода объекта, тогда гораздо сложнее.
Именно это есть в Zend Framework.
Тебе не надо юзать весь фреймворк, там lose coupling (слабая связь), берёшь только то, что связано с кешем — и всё, просто как либу юазешь.
Плюсы: – стандартно, и поддерживать код не тебе – поддерживает много таких кейсов, втыкать можно и как прослойку для ф-ций, и вывод, и ещё куча вариантов – поддерживается куча хранилищ и правильное их использование (типа именования файлов, чтобы не засиралась FS), в т. ч. каскадная поддержка двух харнилищ – все хвалят ООП зенда, я ваще ни бум-бум в ООПе до сих пор, но очень легко изменил одну важну для меня мелочь, наследовав один из внутренних объектов.
Сэм, можешь показать конкретно кэшер зендовский? И как он устроен, вообще? Если библиотека, это ж её на хостинге включать надо, наверно? Или просто внедряешь как какой-нибудь класс?
Мне кажется, это, во-первых, архитектурно неправильный способ. Если программа изначально не была рассчитана на использование кеша, то нужно:
1. Либо рассматривать всю её архитектуру и аккуратно прикручивать этот кэш в нужном месте, следя, чтоб попутно ничего не отвалилось — и тогда уже не важно, дописыванием «$cacher->» вставляется в код обращение к кэшу или нужно строку полностью переписывать в другой синтаксис, потому что эти затраты усилий несущественны.
2. Либо пойти методом хаков, воспринимая программу как черный ящик, который извне получает данные (через ту же file_get_contents, например), и подсовывать ей вместо стандартной file_get_contents свою реализацию с кешем. Тут уже тоже не до красоты кода, как бы.
Во-вторых, с позиций ООП и метапрограммирования, способность программы познать себя тоже имеет свои границы:
Не скажу за PHP, но попробовал для наглядности представить, заработает ли аналог $cacher->$class->someMethod() в Ruby. Вроде бы, меняем bla.someMethod(foo, bar) на cache.bla.someMethod(foo, bar), при вызове всплываем в cache по method_missing и… получаем фиг. Потому что имя «bla» мы как параметр method_missing получим, а вот нужные нам к нему someMethod, foo и bar — нет. Они остались «снаружи». В Ruby такой код нужно честно заключать в блок и не выпендриваться.
Возможно, на Лиспе такой хитрый макрос заработал бы без проблем, а в «Лиспах для бедных» высокой магии не предусмотрено.
1. $data = $cacher->$class->someMethod();
$class – без $, если ты обращаешься к класу в классе $cacher.
$data = $cacher->class->someMethod();
2. нифига не понятно что нужно.
$cacher = new classCacher;
$data = $cacher->file_get_contents(URL);
если это работает, тогда что?
юзай это конструкцию везде.
т.е. класс classCacher содержит оверрайденную функцию file_get_contents, либо можешь по-другому назвать. в которой проверяется необходимость кеша.
в своих модулях делай инклуд файла с classCacher и оттуда вызывай $data = $cacher->file_get_contents(URL); вместо старых $data = file_get_contents(URL);
тогда сразу думай над расширением и назови модуль не classCacher, а classFile, а в нем уже реализуй всю работу с файлами.
тогда все твои проекты будут подключать нужные модули и юзать их не задумываясь о реализации.
Clr, но такой способ и в новых проектах был бы очень удобен. Очень просто и прозрачно.
Tserj, ты вообще не понял, но, честно говоря, не знаю уж как объяснить :) Не надо к примерам привязываться, они именно примеры, абстрактные. В том-то и дело, что вместо file_get_contents() может быть любая другая функция, в том числе методы объектов, и я хочу, чтобы можно было добавить кэширование простым дописыванием $cacher-> перед вызовом функции/метода.
Дошло, как это сделать. Теоретически. Если язык поддерживает два спец метода: первый — аналог method_missing (это вроде любой скриптовый язык поддерживает :) ) ; второй — метод, который будет вызван при попытке вызвать объект как функцию (понятия не имею, есть ли он в Ruby или PHP, никогда не приходилось пользоваться).
В этом случае, поймав method_missing, нужно вернуть промежуточный объект, который перехватит следующий method_missing (попытку отрезолвить someMethod и.т.п). И повторять это до тех пор, пока мы не поймаем попытку вызвать объект как функцию. В этом месте нам известен полный контекст вызова — имена всех bla.bla.bla и параметры.
Сейчас поищу, может быть и есть такое в PHP.
Кажется и до меня дошло, но появился вопрос – с чего ты решил, что какой-то метод должен что-то вернуть? А если ничего не вернет – что будешь кешировать? А если объект вернет? Бредовый подход.
aktuba, очевидно, это решается чтением кода программы ;)
Aktuba, да почему бредовый-то? Очевидно, нужда кэшировать может быть только в том случае, если что-то возвращает данные. Скорее, бредовый подход, если функция/метод не возвращает ничего.
2Clr: я к тому, что не всякий метод можно подобным образом закешировать ;). Вот еще пример:
class Model {
… public function get_posts(&$count, &$posts, &$error) { $count = DB::query(…); if ($count > 0) $posts = DB::query(…); return $count > 0; }
…
}
Код абстрактный, но более-менее реальный, довольно часто вижу подобные методы. Как будет кешироваться в данном случае?
>Очевидно, нужда кэшировать может быть только в том случае, если что-то возвращает данные.
Еще раз – функция не всегда возвращает данные.
>Скорее, бредовый подход, если функция/метод не возвращает ничего.
Один пример привел, второй – функции апдейтов/реплейсов/промежуточной обработки данных и т.д.
Нет такой цели — кэшировать всякий метод. Кэшировать нужно данные, а данные мы всегда откуда-то получаем. Твой абстрактный пример не получится закэшировать с моим подходом, очевидно, только правкой кода класса.
Я уж не говорю о том, что метод в твоём примере ужасно уродливый. Ты называешь его get_posts(), но при этом метод выводит не посты, а их количество. Где логика?
(Посмотрел на код, вспомнил отдельные элементы синтаксиса, закрыл лицо руками и горько заплакал.)
Бедные, несчастные люди. Им приходится возвращать данные через ссылки в параметрах, потому что получить из функции массив и присвоить его элементы произвольному числу переменных — в этом языке так до сих пор и не научились.
«Ты называешь его get_posts(), но при этом метод выводит не посты, а их количество. Где логика?»
Вроде бы похоже, что всё же посты: $posts = DB::query. Другой вопрос, правда, зачем возвращать отдельно count — вызывающая сторона сама в состоянии посмотреть длину массива. И зачем возвращать error — можно либо сразу исключение кидать, либо возвращать return-ом, опять же, единственный объект класса BlaBlaSomeFrameworkError вместо массива постов.
Так $posts по ссылке передается, а сам метод возвращает (return) просто количество. В общем, непонятная какая-то штуковина :) И вообще, мы сильно отвлеклись.
К сожалению, пока я так и не нашёл способа решить свою задачу. Изврат какой-то в голову лезет, типа если присваивать значение через двойное равно: $data = $cacher = someMethod(); и в $cacher уже как-то бы стопорилось выполнение someMethod, если кэш для него есть. Теоретически это сделать можно, но точно изврат вообще.
>Я уж не говорю о том, что метод в твоём примере ужасно уродливый. Ты называешь его get_posts(), но при этом метод выводит не посты, а их количество. Где логика?
Тут ты не прав – метод возвращает количество постов, сами посты, текст ошибки (если были ошибки) и результат работы метода. Не забывай, даже при возврате постов могут происходить обновления базы, например обновление счетчиков внутренних. Ну это мы отвлеклись.
По сути поста – твой метод получается не прозрачный, а очень ограниченный огромными заборами.
>Вроде бы похоже, что всё же посты: $posts = DB::query. Другой вопрос, правда, зачем возвращать отдельно count — вызывающая сторона сама в состоянии посмотреть длину массива.
Потому что для пагинатора, например, надо общее кол-во в базе, а не кол-во в результате.
>И зачем возвращать error — можно либо сразу исключение кидать, либо возвращать return-ом, опять же, единственный объект класса BlaBlaSomeFrameworkError вместо массива постов.
Зависит от ситуации. Не всегда удобно/выгодно/правильно выкидывать исключения. Пример-то абстрактный ;).
>Так $posts по ссылке передается, а сам метод возвращает (return) просто количество. В общем, непонятная какая-то штуковина :) И вообще, мы сильно отвлеклись.
А если внимательно посмотреть? =) Кол-во возвращается по ссылке, в return возвращается результат работы метода. И в этом очень часто есть смысл ;).
Да, отвлеклись. Твой подход все-равно считаю не правильным. Вот тоже самое, но более прозрачно, более удобно и логичнее:
$data = $cacher -> get(md5(URL));
if ($data === NULL) { $data = file_get_contents(URL); $cacher -> set(md5(URL), $data);
}
Суть та же самая, но использовать можно намного большими способами. Этот метод используется почти во всех кешерах (со своими вариациями, конечно).
> если присваивать значение через двойное равно
тогда $cacher получит результат someMethod(), т.е. уже после выполнения someMethod. и никак его не застопорить
«по ссылке передается, а сам метод возвращает»
Ага, это я неверно интерпретировал семантику слова «возвращает».
Эм… Насчёт варианта с присваиванием не понял: в PHP можно переопределять для класса оператор присваивания как в плюсах? Вроде же нельзя было…
К тому же, если и можно, someMethod() выполняется раньше присваивания.
Тебе, по сути, нужны ленивые вычисления, в PHP нет их, разве что вручную эмулировать на eval-ах.
У меня совсем оффтоповый вопрос, раз уж тут собрались люди, которые еще помнят PHP, в отличие от меня. :)
Там есть какой-нибудь нормальный способ делать замыкания? В Руби я могу в вызов метода передать блок, хранящий контекст своего вызова, в javascript — функцию, которая так же привязывается к контексту в момент своего создания.
Когда мне 2 (или уже 3) года назад потребовалось на PHP делать коллбеки, я поймал себя на том, что занимаюсь реализацией на PHP кривого аналога лиспа на eval-ах и понял, что пора срочно менять область деятельности… :)
Сейчас что-нибудь изменилось?
2 Clr это?:
выдержка из DbSimple_Generic dklab.ru/lib/DbSimpl…
/** * callback setErrorHandler(callback $handler) * Set new error handler called on database errors. * Handler gets 3 arguments: * – error message * – full error context information (last query etc.) */ function setErrorHandler($handler) { $prev = $this->errorHandler; $this->errorHandler = $handler; // In case of setting first error handler for already existed // error – call the handler now (usual after connect()). if (!$prev && $this->error) { call_user_func($this->errorHandler, $this->errmsg, $this->error); } return $prev; }
Неа, вот такое:
color = color.map(function(v){return Math.min(Math.max(Math.round(v * 255), 0), 255);});
(Жабаскрипт)
Или вот такое:
fragments_2lines = fragments.reject { |n| n.split(»\n»).size > 2 }
(Руби)
На самом деле не совсем оффтоповый вопрос, потому что если оно есть в PHP, то безобразное
$data = $cacher -> get(md5(URL));
if ($data === NULL) { $data = file_get_contents(URL); $cacher -> set(md5(URL), $data);
}
легко и просто превращается в что-то типа
$data = $cacher->read(URL, {file_get_contents(URL)});
А это практически то, что требуется Тормозу, за исключением мелких различий в синтаксисе.
ну тогда уж:
$cache = new cacher();
$cache->get(‘file_get_contents()’, ‘someurl’);
—————————
class cacher { function get($func, $param) { echo «Анализ, нужен ли кеш, если нет – вызов обычной функции через call_user_func»; }
}
И в каком монтексте она будет вызвана? В идеале нам нужно:
а) вызвать её в контексте функции, в которой вызов изначально был до добавления кэша в программу и
б) иметь возможность вызвать произвольный код, а не просто дёрнуть одну функцию из глобального пространства имён.
Собственно, я 2 года назад практически так и извращался, как в твоём примере. :)
идея кеша – чтобы считывать кеш, прежде чем идет вызов file_get_contents с урла.
к сожалению php, насколько я помню эвентов не поддерживает. т.к. выполнение происходит поточное и после отработки кода – выход, а не ожидание эвентов.
в контексте глобальной функции file_get_contents – вызвать не получится.
Я не знаю, что вы называете эвентами, но то, о чём я говорю, называется замыканиями. Элементарно передать дальше по стеку свой контекст и затем дёрнуть нужный кусок кода из вложенного вызова другой функции. Даже Паскаль это умел.
В общем, понятно, PHP ничуть не улучшился. :(
Clr, в пхп 5.3 есть анонимные функции, вроде на забре была статья про замыкания на пхп
$cacher->with($instance)->someMethod();
а еще можно Cacher::with($instanse)->someMethod()
Cacher::with()->file_get_content();
заодно у ментода with может быть второй параметр $lifetime
а в 5.3 нужно через замыкания конечно делать
Cacher::get(function() { return file_get_content(); });
Ответил в блоге.
ну и я внесу свои 10 коп.
include «test.php»;
include «cache.php»;
$t = new test();
class cacher { function __call($func, $param) { echo «<br>$func and $param<br>»; echo «Анализ, нужен ли кеш, если нет – вызов обычной функции через call_user_func»; } }$cache = new cacher();
$cache->{«t->mm(‘test’)»}();
————————————————
————————————————
class test { function mm($mes) { echo «test2»; return $mes; } }собственно, что происходит, когда ты вызываешь: $data = $cacher->$class->someMethod();
1. исполняется $class->someMethod() и возвращается результат.
2. php пытается сконвертить «результат» в стрингу и выполнить $cacher->«результат».
3. Т.к. «результат» представлен ссылкой в памяти – php выдает эррор.
*******************************
что происходит при: $cache->{$class->someMethod(‘params’)}();
1. исполняется $class->someMethod() и возвращается результат в виде строки.
2. исполняется $cache->«результат/название функции»()
*******************************
что происходит при: $cache->{«class->someMethod(‘params’)»}();
1. исполняется $cache-> с методом в виде строки «class->someMethod(‘params’)»
вроде то, что тебе нужно.
2Clr
Наводишь панику по поводу ПХП, не зная, что умеет ПХП. Пальцегнутие какое-то, типа вы все дети малые, на отстойном языке что-то пытаетесь делать, другое дело я, мегаперец.
>Бедные, несчастные люди. Им приходится возвращать данные через ссылки в параметрах, потому что получить из функции массив и присвоить его элементы произвольному числу переменных — в этом языке так до сих пор и не научились.
Это пхп умел еще хер знает когда, если не знаешь, не значит, что этого нет.
>Там есть какой-нибудь нормальный способ делать замыкания?
Выше уже сказано было про анонимные функции, вот еще ссылка из первой страницы гугла
www.recessframework….
Тормоз, допустим у тебя получилось создать универсальный кэшер. А как его внедрить в готовый проект? Переписать вызов каждой кэшируемой функции? А если проект большой? А если в нем не один человек работает? я к тому, что кроме универсальности есть много других сложных вопросов.
Мне кажется, что лучше делать конкретные классы, а потом обобщать. А сразу сделать универсально – не вариант.
che
Спасибо.
«Ну тогда через них и имеет смысл делать.» — хотел написать сначала я.
А потом нагуглил такое:
$multiply = function($multiplier)
{
return function($x) use ($multiplier)
{
return $x * $multiplier;
};
};
И тут облом. Можно как-нибудь заставить интерпретатор сделать по умолчанию use ко всем локальным переменным родительской функции?
tserj
$cache->{«class->someMethod(‘params’)»}();
Возможно, не совсем понял, но вижу 3 проблемы.
1. Ты передаешь в кэш вызов с именем «class->someMethod(‘params’)», и пишешь, что затем можно его вызвать через call_user_func. Посмотрел доку по call_user_func — она не позволяет вызывать произвольный кусок кода. Тогда уж eval.
2. Если нужно передать аргументы, то получается:
Было: someMethod($url)
Стало: $cache->{«someMethod(’».$url.»’)»}();
3. Что если объект, метод которого мы вызываем, находится в стеке?
quantum
Баттхерт? «За Родину обидно?»
Может и так. А может просто пустозвонов не люблю
В общем, Тормоз, смотри в сторону решения «взял из кеша, работаю с данными», а не «работаю с объектом в кеше». Лишний вызов в начале окупается отсутствием необходимости каждый раз писать большие непонятные конструкции и в случае масштабирования или переиспользования, изменения пройдут легче.
Блин, вы меня запутали! Tserj, твой вариант с фигурными скобками мне не нравится, это уже неудобно.
Пойду посмотрю, что Джек наваял.
Сергей М., вариант «взял из кеша, работаю с данными» плох тем, что вокруг этого «взял из кэша» ещё надо каждый раз городить проверку, надо ли передёрнуть кэш. Неудобно это нифига.
Городи внутри класса кеша, точнее, делай для каждого наследованный класс CacheArticles extends Cache и городи там – в тот момент, когда дергаешь кеш. А программа сама не должна знать, с чем она работает – с сохраненной копией или объектом из базы.
И как ты себе это представляешь? К примеру, есть уже в коде $obj->getData(), как к этому удобно и просто подключить кэш?
Эм, у тебя объект возвращает массив? В случае с $obj->getData() кидок к кешу должен быть инкапсулирован в нее.
Но ты же пять минут назад рассказывал о том, что у тебя сам объект – это и хранилище данных, и обработки над ним. В большинстве таких случаев объект должен загружать данные в конструкторе, там можно повесить кеш на запрос (скажем, там у тебя $this->data=get_file_contents(), а станет if(! $data = Cache::instance()->getCache(‘articles’)) {$data=get_file_contents(); Cache::instance()->setCache(‘articles’)}) .
Либо так, как ты хочешь – у тебя получается CacheArticles::getData($obj), где инкапсулирована проверка на кеш, и если надо setData(), или вызов $obj->getData() в обратном случае. В этом варианте у тебя будет легко расширяемый объект, который можно будет затачивать под разное.
Соответственно, в последнем CacheArticles::getData($obj) не должен возвращать новый экземпляр класса – нет смысла, он должен менять его по ссылке, сеття инфу.
А какая разница, массив или строка? Вот это как раз кэшер может и должен определять сам и поступать прозрачно и адекватно с данными.
Блин, я что, такой косноязычный что ли? :) Ужас. Меня никто не понимает. ВНЕДРЕНИЕ КЭШЕРА НЕ ДОЛЖНО НЕСТИ ИЗМЕНЕНИЯ В ИСХОДНИКИ УЖЕ СУЩЕСТВУЮЩИХ КЛАССОВ.
В этом суть. Чтобы внедрение было простейшим: создал объект, подставил его прокладкой перед вызовами функций/методов и всё.
коменты не читай сразу отвечай:
зачем огороды городить, возьми нормальный кеш апи и используй для кеша приблуду (мемкеш етц)
кстати, очень хорошей идеей есть кеширование блоков в шаблоне а не классов в коде, да еще и с таким извратом как в оппосте (всмысле кода дописывать надо к каждому вызову что бы его закешировть)
Мемкэш не подходит — не подходит всё, что требует серверной настройки. Кэшер, который я делаю, должен работать на любом виртуальном хостинге.
Про какие шаблоны ты пишешь, я не понял. Можно конкретней? О чём речь?
>Блин, я что, такой косноязычный что ли? :) Ужас. Меня никто не понимает.
Да это ты не понимаешь, что тебе пишут другие.
>ВНЕДРЕНИЕ КЭШЕРА НЕ ДОЛЖНО НЕСТИ ИЗМЕНЕНИЯ В ИСХОДНИКИ УЖЕ СУЩЕСТВУЮЩИХ КЛАССОВ.
А разве метод, который я дал, изменяет существующие классы? Нет, наоборот – он служит оберткой для данных классов и при этом позволяет использовать кеш везде, где необходимо.
>кстати, очень хорошей идеей есть кеширование блоков в шаблоне а не классов в коде, да еще и с таким извратом как в оппосте (всмысле кода дописывать надо к каждому вызову что бы его закешировть)
Плохая идея, кстати. Во-первых, в кеше будет хранится много мусора (оформление), во-вторых, при любом изменении верстки надо будет сбрасывать кеш.
>Мемкэш не подходит — не подходит всё, что требует серверной настройки. Кэшер, который я делаю, должен работать на любом виртуальном хостинге.
Вот ты упертый =). В моем примере ты видел, какой кеш используется? Посмотри github.com/kohana/ca… – наглядный пример правильного кешера – использующие его классы не должны знать, какой кеш используется. Их задача – сохранить в кеш или получить из кеша данные.
Aktuba, твой метод ничем от моего не отличается, он точно также не будет работать с методами объектов. Ты ведь про этот вариант? Или я что-то пропустил?
Насчёт оффтопа с кэшем страниц. А в чём проблема сбросить кэш при смене вёрстки? Раз в секунду вёрстку меняешь что ли? Но вообще-то здесь совсем про другой кэш, только про сырые данные.
Пример в гитхабе от Коханы тривиальный, я так могу сделать. Ты задачу читал? Это не удобно! Блин.
>Aktuba, твой метод ничем от моего не отличается, он точно также не будет работать с методами объектов. Ты ведь про этот вариант? Или я что-то пропустил?
Упустил, причем самую важную деталь. Ты пытаешься обернуть функцию/метод, чтобы закешировать результат, а мой метод получает данные и только потом кеширует. Моему кешеру просто не надо знать, как и что работает – он берет данные и сохраняет их. В твоем же случае, надо всегда знать, куда идет вызов и как он обрабатывается… Ты пытаешься экономить пару строк кода в ущерб удобству. Посмотри, сколько вариаций вызова кешера у тебя уже сейчас:
$data = $cacher->file_get_contents(URL);
$data = $cacher->$class->someMethod();
$data = $cacher->class::someMethod();
>Насчёт оффтопа с кэшем страниц. А в чём проблема сбросить кэш при смене вёрстки? Раз в секунду вёрстку меняешь что ли?
А я где-то сказал, что это проблема? =) Нет, не проблема, но подход бредовый. Ну и первую часть предложения ты пропустил – лишний мусор в кеше.
>Пример в гитхабе от Коханы тривиальный, я так могу сделать. Ты задачу читал? Это не удобно! Блин.
Ппц… Читал и отвечал, если не заметил. Поэтому и говорю – твой подход абсолютно не удобный, не говоря уже о дальнейшей отладке. Например, $data = $cacher->file_get_contents(URL); Как будешь ловить ошибки в file_get_contents? Как будешь обрабатывать их? Или этим кешер будет заниматься? Тогда кешер ли это?
Тормоз. Собственно, про методы. Зачем тебе вообще методы как-то оборачивать? Тебе нужно банально взять значение из кэша по ключу. Ключем может служить что угодно: URL, URL + имя, число и т.п. — нужно в каждом конкретном случае смотреть, что именно будет являться уникальным ключем.
Решение в лоб:
value = cache.get(url) || (cache.set(url, someBlaBlaToGetRealData(url)))
Смотрим. Понимаем, что имеет место избыточный копипаст кода. Переписываем так, как примерно ты хочешь:
value = cache.read(url) {someBlaBlaToGetRealData(url)}
В Рубях всё ок.
Теперь пытаемся ту же идиому оформить на PHP. Обнаруживаем, что чтобы сделать такое же замыкание, в PHP нужно каждый раз писать километровую строчку кода.
Обламываемся и возвращаемся к первому варианту, потому что так короче и проще.
Как-то так.
что-то мне тут скучно и грустно находится становится. все им велосипеды из 90х, все им виртуальный хостинг. у людей есть все что бы делать крутые проекты, воплощать мечты, реализовывать идеи. вместо этого они пишут кешер на файликах для виртуального хостинга…
Aktuba, отвечаю задом-наперёд. Ошибки в file_get_contents() имеешь в виду, если данные битые? Но ты ведь в любом случае проверяешь их, с кэшем или без, не так ли? Так что это снаружи кэшера. Если данные корявые, кэш сбрасываем.
Про кэш страниц категорически не согласен. Этот «мусор» никого не затягивает, зато с таким кэшем ты отдаёшь сразу всё без дополнительных запросов и обработки. Пусть не во всех случаях, но кэш страниц — часто превосходнейшее решение. Нет в нём ничего плохого. Особенно если этот кэш вообще сразу в html делается в нужном каталоге.
Про «ущерб удобству»… в чём неудобство-то, если бы удалось реализовать мой вариант? Как раз сплошное удобство.
Твой подход — традиционный, не вижу смысла его обсуждать, его реализация очень проста и обмусолена уже везде. Её преимущества и недостатки ясны.
docs.djangoproject.c…
да и без якоря прочесть полезно
Миша, да ладно тебе дай херней пострадать. Не все же с высокой нагрузкой работать :)
Миша, аристократ хренов :) Мы тут про PHP говорим, я без тебя знаю, что существуют такие всякие замечательные технологии.
Я вот что не могу понять, почему __get() не отлавливает вызов, даже если убрать знак доллара? $cacher->$test->someData() и $cacher->test->someData() одинаково нерабочие…
>Aktuba, отвечаю задом-наперёд. Ошибки в file_get_contents() имеешь в виду, если данные битые? Но ты ведь в любом случае проверяешь их, с кэшем или без, не так ли? Так что это снаружи кэшера. Если данные корявые, кэш сбрасываем.
Нет, не так. А если это не file_get_contents, а curl? А если надо обрабатывать заголовки (редиректы, 404-ю и т.д.) и исходя из этого работать с данными? Возможна ситуация, что новые данные получить не удалось, значит надо использовать старые? Как это будешь обрабатывать? А про exception-ы слышал? Их часто используют… Ну и т.д.
>Про кэш страниц категорически не согласен. Этот «мусор» никого не затягивает, зато с таким кэшем ты отдаёшь сразу всё без дополнительных запросов и обработки. Нет в нём ничего плохого. Особенно если этот кэш вообще сразу в html делается в нужном каталоге.
Если использовать SSI – в таком подходе есть смысл. Если нет – нет смысла, php все-равно будет работать и экономия в 0.0000001 сек на echo ничего не даст, кроме гемороя в дальнейшем.
>Твой подход — традиционный, не вижу смысла его обсуждать, его реализация очень проста и обмусолена уже везде. Её преимущества и недостатки ясны.
>Про «ущерб удобству»… в чём неудобство-то, если бы удалось реализовать мой вариант? Как раз сплошное удобство.
Ты мои примеры то читал? В чем «сплошное удобство» в разных вызовах кешера? В чем «сплошное удобство» замены функций/методов? В чем «сплошное удобство», когда не всегда можешь использовать свой кешер (пример давал – функция с передачей параметров по ссылке)?
Поэтому на данном, традиционном, подходе показываю тебе неверность твоего направления. Упрямость иногда помогает, но чаще мешает.
Бум думать.
причем тут пхп, ты посмотри как умные люди делают, а пхп я еще не забыл, и не скоро забуду эти кошмары ж)
«$cacher->$test->someData() и $cacher->test->someData() одинаково нерабочие…»
Потому что test сам по себе не есть вызов метода?
$cacher->test()->someData()
Clr, в таком контексте это должно быть свойство объекта, а не метод. Объект ведь представлен в коде как обычная переменная. Да и всё равно даже если убирать $, это уже непоследовательно… хочется что-то такое, что работало бы одинаково просто, как с функциями, так и с методами объектов. Без нюансов.
Миша, про кэширующие обёртки с cache-start, cache-end тоже думал, но это как-то не очень красиво. Костыли.
>Я вот что не могу понять, почему __get() не отлавливает вызов, даже если убрать знак доллара? $cacher->$test->someData() и $cacher->test->someData() одинаково нерабочие…
писал об этом выше. тут нужны скобки. а т.к. вышеперечисленное тебя не устраивает – ВНЕДРЕНИЕ КЭШЕРА НЕ ДОЛЖНО НЕСТИ ИЗМЕНЕНИЯ В ИСХОДНИКИ УЖЕ СУЩЕСТВУЮЩИХ КЛАССОВ. – не реализуемо, переписывай код, заранее учитывая будущие потребности в архитектуре.
Zend_Cache можно использовать как отдельный класс, без остального фреймворка.
Чтобы провернуть трюк с кэшированием результатов выполнения функций у объектов можно сделать так:
$cacher->getObjectCache($obj)->data()
Где getObjectCache будет возвращать новый кэшер.
Но это конечно стремное негибкое решение. Лучше заверни работу с удаленными данными в класс, который сам будет решать, что брать из кэша, а что запрашивать. И вместо file_get_contents(URL) пиши $remote->get($url);
Каменты не читаю.
Zend_Cache мне уже Samlowry советовал и даже в переписке показал примеры — всё не то, замудрёно. Ну а $remote->get() совсем плохое решение, хуже некуда.
Тормоз, ну да, чёт я туплю.
Вот попробовал написать такое:
<?php
class CCache {
private $context = array();
public function __get($name) { $this->context[] = $name; return $this; }
public function __call($name, $x) { $this->context[] = $name; var_dump($this->context); var_dump($x); }
}
$cache = new CCache();
$cache->foo->bar->baz(‘blabla’);
?>
Вроде работает:
vadim@host3:~$ php5 123.php
array(3) { [0]=> string(3) «foo» [1]=> string(3) «bar» [2]=> string(3) «baz»
}
array(1) { [0]=> string(6) «blabla»
}
vadim@host3:~$
Ток что-то половина переводов строк исчезла при постинге коммента. Ну в общем, понятно и так. :)
А как с производительностью? Представь, если объект, в котором нужный метод — огромный. И ты, получается, создаёшь ещё один такой же.
И, кстати, он ведь уже создан будет ещё до кэшера. Возникнет конфликт.
Эм… Я не создаю объекта. Кэшер возвращает в качестве своего свойства самого себя, чтобы отследить всю последовательность «имён по стрелочкам».
Другой вопрос, что ты потом будешь делать с этой последовательностью имён. В PHP есть возможность из вызванной функции отрезолвить переменную вызвавшей функции? Я чёт сходу в доке не нашел, а сам не помню. :)
Так, надо разобраться и поэкспериментировать. Возможно, я не правильно тебя понял.
Хотя, удаление $ тоже плохо :)
Тьфу, забыл про амперсанд. Вроде же в PHP надо так: function &__get($name), чтобы действительно дублей кэша не понасоздавалось.
короче, развели тут, идите работать а про кеширование в книжке почитайте. и не тратьте время на эту псевдомагию пхп, все равно ничего не добьетесь. \0
@Clr: если ты объект возвращаешь то уже ненада лет 7 как
Миша, хватит ныть уже, пришел обхамил php ткнул ссылкой в фраймверк питоновский (вообще принято тыкать сразу в рельсы). И вопишь чего то про загнивающий капитализм :D В общем типичное поведение – «Все п****асы, а я — д’Артаньян».
не про то я воплю, да и сам такой же, велосипедов в свое время объелся
я ткнул ссылкой в удачный дизайн такой системы, имхо конечно
простое апи, широкие возможности, это идет вразрез с оппостом, причем идет в более правильном, опять же имхо, направлении
а так мне за державу обидно, как я уже писал, могли бы делом занятся, вместо этого тратите время. и это люди восхваляющие сигналы!
Миша, я на нём сто лет не писал. Помню, что раньше эти амперсанды где-то были нужны.
Тормоз. В общем, что-то мне гугл не хочет признаваться, как get variable in caller context и прочие аналогичные вопросы.
Поэтому, чтобы не тянуть кота за половые признаки, я бы просто сделал так:
// Протип кэша
class CCache1 {
private $data = array();
public function get($name) { return array_key_exists($name, $this->data) ? $this->data[$name] : false; }
public function set($name, $value) { return $this->data[$name] = $value; }
}
$cache = new CCache1();
// Пример использования
function getBlaBla($bla) { return «qwerty»;
}
$key1 = ‘foo1’;
// Было без кеша:
$value = getBlaBla($key1)
// Стало:
$value = $cache->get($key1) || $cache->set($key1, getBlaBla($key1));
Мда. И это удобно, по-твоему?
Миша, очевидно, PHP-шники делятся на 2 категории: те, которые на нём пишут, потому что за это имеют деньги, и те, которые на нём пишут, потому что прочитали аж целый один самоучитель. И тех, и других переманивать на другие языки бесполезно: первые сами перейдут, когда появится возможность, а у вторых упоминания о более других языках и фреймфорках вызывают приступы баттхерта.
Тормоз
Это самое удобное, что мне пришло в голову на этом языке.
Есть 3 спосба решить такую задачу.
1. Использовать $cache->get($key1) || $cache->set($key1, …
Самый простой и доступный на любом языке.
2. Отслеживать «последовательность стрелочек», обращаться к контексту вызывающей функции и выполнять «вручную» последовательность этих вызовов. Это то, что ты хочешь сделать. Как отслеживать, я показал выше. Но как обратиться к контексту вызывающей функции, я не знаю. Видимо, никак.
Так что способ не работает. Кроме того, архитектурно он ужасен.
3. Использовать замыкание.
Самый архитектурно правильный способ. Проблема в том, что код для замыкания получается в PHP длиной в километр. Чисто с практической строны такой метод не подходит. Сравни:
Ruby:
value = cache.read(key1) {getBlaBla(key1)}
и
value = cache.read(key1) {object.getBlaBla(key1)}
А в PHP:
$value = $cache->read($key1, function () use ($key1) {return getBlaBla($key1)} );
и
$value = $cache->read($key1, function () use ($key1, $object) {return $object->getBlaBla($key1)} );
Итого, остаётся первый способ.
Clr, раньше копия создавалась, и чтобы этого не происходило использовали &
Тормоз, мне кажется проще написать обертку для запроса страничек и не парить мозг :) Меня в твоем кешире напрягает отсутствие логики определения уникальности ключа по которому закэшированное будет вытаскиваться и то когда сохраненное перестаёт быть валидным.
Clr, вот я голову ломаю уже несколько дней, как сделать красиво. Позавчера вообще проснулся в 6 утра и не мог уснуть, потому что будоражили мозг идеи, которые сразу решил проверить :)
Che, кэш нужен не только если странички с удалённых серверов получаешь, разные ситуации бывают.
Логика определения уникальности ключа есть, просто она за кадром, т. к. это уже детали реализации. Ключ делается простейшим хэшированием имени функции/метода и параметров.
Тормоз, так написал «простейшим», как-будто это два пальца об асфальт.
$url = ‘http://ya.ru’
$data1 = $cacher->file_get_contents($url);
$url = ‘http://yandex.ru’
$data2 = $cacher->file_get_contents($url);
Он у тебя будет опираться на значения переменных? А если в кешер передавать два разных объекта одного класса?
Два кэша будет, значит. Если они в коде у тебя до кэшера раздельно сделаны, не просто так же? И в твоём примере для $data1 и $data2 тоже будет два кэша. Пример, правда, извини, глупый какой-то. Универсальный кэшер должен знать о различных URL конкретных сайтов? :)
прочитав книгу «начертательная геометрия» старшеклассник Петя был глубоко поражен и сразу принялся за конструирование УНИВЕРСАЛЬНОГО ТРАНСПОРТНОГО СРЕДСТВА, да такого чтоб и под воду, и в недры земные и в космос слетать можно было.
Я кстати, про то что второй способ архитектурно ужасен согласен.
Вот читаю я код и встречаю метод типа
$cacher->$class->foo()
Ага, думаю я – значит у нас переменная $class храниться в классе Cacher – пойду посмотрю.
И облом.
Ну и во вторых сама идея синтаксически облегчить внедрения кэшера видиться мне способом облегчить себе отсрел ноги.
Да и глупо – ну сколько времени нужно тобы вместа 20 символов, набрать 100. Ну на 60 секунд больше. А уж с макросом да еще и в хваленом vim – все автоматом дожно делаться.
На мой взгляд это очень мало по сравнению с тем временем которое нужно чтоб проанализировать функцию, чтобы понять а нужно ли тут кэширование. А не будет ли блокировок, а не получиться ли медленее и т.п.
И провоцирует на бездумное втыкание данного кэша куда угодно :)
Идея с замыканиями хороша на мой взгляд. Но не сильно лучше стандартного: get() || set
Да и вообще читая код я бы предпочел видить стандартный и понятный вариант.
А ты вроде же собираешь писать класс который будешь в публичный доступ вкладывать, или нет?
т.е. читать его будут чаще, чем дописывать :)
P.S. Тормоз, а зачем тебе УНИВЕРСАЛЬНЫЙ кэшер?
Да засела идея в мозг, вот и всё. Хочу красиво и удобно сделать. Долго думал о том, как удобно, и пришёл к выводу, что именно такой способ самый клёвый.
Как вариант (сказочный, тоже из области фантазий) — объявляешь в начале объект кэшера и перечисляешь в нём функции и методы, которые необходимо кэшировать. И чтобы дальше автоматически всё делалось. Но это тоже почти нереально.
Т.е. префекционизм :)
Бывает – главное чтоб проходило.
То что ты хочешь наверно реально. По крайне мере в некоторых языках програмирования.
Но это же ужастно – т.е. ты читаешь код и из него не фига не видишь – вот тут у тебя кэшируються данные, или не кэшируется.
А от этого же меняется логика работы с данными.
Опять же как в твоем лучае отключать кэш для некоторых объектов.
Как обеспечить многопоточную работу – тебе придеться делать блокировки.
Вот у тебя допустим вызывается некоторая функция в множестве потоков. К ней ты добавляешь свое универсальное кэшировние.
И что дальше? Что происходит если по середине извлечения данных из кэша эти данные кто-то меняет?
Нужна синхронизация.
Синхронизации на уровне выхова функций как я понимаю в PHP нет (или есть?). Ее нужно делать руками.
Ну и т.п.
Т.е. ты бы все же сделал бы простой вариант кэшера, сделал бы 2-3 проекта на нем, а потом уже бы думал как улучшить.
Делать сразу универсально это зло. И это не продуктивно.
Хотя, нужно заметить, что сам факт наличия тут такого количества комментов с разной информацией – это уже полезно :)
Вообщем с идеей нужно переспать. Вернись к ней через время.
P.S. Все же принцип что нужно делать минимум для текущей задачи очень хороший.
Как это не вижу, где кэшируются, а где нет? Где кэшер подставлен — там кэшируются, всё отлично видно. Блокировки тоже предусмотрены, как и ключи — я не стал рассказывать полностью всю реализацию класса, зачем? Это всё уже из другой оперы.
Кэшеров (не в ООП) я уже дофига делал, большинство проектов требуют кэшик.
P.S. С этой задачей я сплю уже не первую ночь :) Пока ничего лучше не придумал, увы. И я буду расстроен, если придётся идти по традиционному пути, это как-то некрасиво… просто привычно.
«Как вариант (сказочный, тоже из области фантазий) — объявляешь в начале объект кэшера и перечисляешь в нём функции и методы, которые необходимо кэшировать»
есть красивое решение, только нужно PECL apd >= 0.2 (не спрашивай меня что это;))
идея: в проект инклудится файл с оверрайдами базовых функций – т.е. к ним подключается кеш. все.
минус: пока не придумал способ оверрайдить методы кастомных классов.
мануал:
php.net/manual/en/fu…
надеюсь хотя бы проблема file_get_contents теперь будет решена
Проблемы с file_get_contents() не было изначально, ты так и не понял ничего :)
Два кэша будет, значит. Если они в коде у тебя до кэшера раздельно сделаны, не просто так же? И в твоём примере для $data1 и $data2 тоже будет два кэша. Пример, правда, извини, глупый какой-то. Универсальный кэшер должен знать о различных URL конкретных сайтов? :)
—-
Ты не описал как кэшер будет основываясь на «$cacher->file_get_contents($url)» будет делать разные кэши в зависимости от значения $url.
А в чем проблема? Хэш из урла только сделать.
«префекционизм»
Видимо, имелся ввиду перфекционизм. Опечатка получилась интересная. :)
Тормоз
«Как вариант (сказочный, тоже из области фантазий) — объявляешь в начале объект кэшера и перечисляешь в нём функции и методы, которые необходимо кэшировать.»
Вариант совершенно не сказочный в Ruby и Javascript (и наверное, в Питоне) — любой метод можно переопределить своей реализацией. В том числе, автоматически по списку на лету сгенерированной функцией и вообще как угодно.
Тормоз, смирись. :) То, что ты пытаешься сделать, лежит за пределами возможностей бывшего шаблонизатора для подстановки переменных в HTML.
короче, вы меня все так выдрочили что я сделал то что вы хотите, а теперь зацените насколько практично будет использование ЭТОГО, и я не думаю что у вас получится как-то более изящно это сделать
вывод надеюсь все сделают сами
cache.php: dumpz.org/24789/
test.php: dumpz.org/24790/
$ mkdir tmp
$ chmod 777 tmp
$ php test.php
конечно, код не претендует ни на что, написан плохо, плохо читаем плохо понимаем и там еще не хватает кучи проверок на типы и прочее, прочее, прочее, это просто PoC. наслаждайтесь :Р.
Che, кончай троллить. Ты же опытный программист, зачем постоянно такие вбросы делаешь? Я не верю, что ты не понимаешь, как реализовать простейший функционал. Но ладно, спешлфою: md5($name_func.join('_', $args)). Запиши в свой набор «золотые сниппеты», чтобы не забыть.
Миша, я сейчас заметку про метамерчанты допишу и посмотрю. Очень любопытно, но сдерживаюсь :)
Миша
В принципе, для изложения идеи достаточно было написать строчку:
cache_decorator(array($object_instance, ‘fetch_value’), $arguments, 1);
Только чувствую, Тормозу это совсем не понравится. :)
Там есть одна неточность в определении домена. Нужно в любом случае хеш аргументов дописывать, а не только если объект, но это вы уж сами подкрутите.
И еще, конечно, сделать basename для ключей. Но вообще я бы это не использовал. Берите что-то свободное и покрытое тестами.
@Clr потому я так усердно и пытаюсь его вразумить. Он слишком сильно увлекся сахаром, ему бы по рукам надавать да чтоб работал, а не годы молодые на б-гомерзость разную сливал.
Ладно, мертворожденный, расходимся:
michael@red:~$ php -r ‘echo spl_object_hash(new ArrayObject(array(1)));’
000000006ef7650600000000607414a5michael@red:~$ php -r ‘echo spl_object_hash(new ArrayObject(array(1)));’
000000000badb4330000000053716c72michael@red:~$ php -r ‘echo spl_object_hash(new ArrayObject(array(1)));’
0000000035c2d2670000000059fc16f3michael@red:~$ php -r ‘echo spl_object_hash(new ArrayObject(array(1)));’
000000002c1d119700000000162c2f13michael@red:~$
Ты видишь сколько проблем? И это только начало. А что в итоге, это ли идеальный код и к такому ли надо стремиться? К универсальной магии или к универсальному интерфейсу? К использованию внутри или снаружи? Ладно, я завязываю, парни.
Ага, дописал заметку про метамерчанты и теперь посмотрел, что придумал Миша.
Вот что я вам скажу, многомногоуважаемый Михаил! Простите, не знаю, как по отчеству. Очень лестно, что такое светило джангорубитехнологий потратило своё драгоценное время на велосипедостроительную малышню. Случаи, когда выдающиеся специалисты такого ранга спускаются в чернь — входят в историю. Мы никогда этого не забудем, Михаил. СПАСИБО!!!
Но, к сожалению — ваше решение полная хрень и не решает поставленную задачу. И вы совершенно напрасно не читаете комментарии, зря повторяетесь.
Тормоз, доказывать больше ничего не буду, но задам вопрос: если уж меняешь вызовы функций, не проще ли менять их полностью, т.е. примерно так:
$cacher -> get(‘file_get_contents’, $url);
$cacher -> get(‘Model::get_posts’, $on_page = 25);
$cacher -> get(array(‘Model’, ‘get_posts’), $on_page = 15);
Так хоть более-менее похожие вызовы.
P.S.: get – только для примера, обозвать можешь как хочешь =)
Ну некрасиво это :( И неудобно нифига.
aktuba, а я предлагал – не подходит :) Вот уперся то, забавно кстати смотреть как все приходят к примерно одному и тому же решению.
Миша, увлекся ты с «правильным» программированием, ну серьёзно оно надо было столько необоснованного мусора привносить в код? Вот смотри мой пример из блога выше – dumpz.org/24800/ реализует практически тоже самое (пример – dumpz.org/24801/), только в N раз компактней и во столько же проще для понимая.
Jeck, да это решение на поверхности. Тормоз уперся, это да… Причем, я считаю данный способ намного лучше, чем то, что хочет сделать Тормоз (хотя и намного хуже «традиционного» способа).
>И неудобно нифига.
Зашибись… А чем не удобно-то для тебя? Я же написал – раз все-равно переписываешь вызовы нет большой разницы как переписать.
Есть разница.
А говоришь «никогда не принимаю доводы «не правильно» без аргументации» =)
Да мне надоело очевидные вещи повторять. Ты всерьез что ли не видишь разницы между полным переписыванием вызова и подстановкой одной переменной перед вызовом?
Серьезно не вижу. Знаешь почему?
$cacher -> count($data);
count – это метод класса или кешируем результат функции count?
В моём кэшере не было бы никаких своих открытых методов кроме resetLast() и resetAll(), так что гадать не о чем.
Ну воспринимай get || set как подстановку одной большой переменной, если так хочется.
Вот ведь упёртый человек. Ну нету, нету в PHP способа кратко записывать замыкания!
Тебе с такими требованиями к синтаксису языка надо кодить на Tcl, Лиспе, Руби или Немерле. %-\
Clr, да не надо тут никаких замыканий, как я думаю.
Тормоз, отговаривать похоже бесполезно? Тогда смотри функции ru2.php.net/manual/e… и ru2.php.net/manual/e… Первой ссылки тебе хватит, думаю. По ней получаешь нужный класс, метод, параметры, а большего тебе и не надо в данном случае.
aktuba, в чём профит от debug_backtrace?
2Clr: да все просто. Jeck предложил вариант нужного Тормозу кешера, но с одной оговоркой – вызов методов класса надо оборачивать своей функцией. Если же в класс Jeck-а добавить debug_backtrace – можно получить данные, что нужно обернуть и обработать все внутри класса, соответственно Тормоз получит именно то, что ему и нужно:
$cacher -> $class -> $method($params);
А можно на кошках объяснить?
Берём пример. Было без кэша:
function fooBar($bla, $dataName) {
$objBla = new ClassBla($bla);
$data = $objBla->data($dataName);
}
Стало с кешем:
function fooBar($bla, $dataName) {
global $cache;
$objBla = getSomeBla($bla);
$data = $cache->objBla->data($dataName);
}
Вопрос: как из метода __call объекта $cache получить доступ к $objBla, который лежит в локальном пространстве вызывающей функции?
Во-первых, вызов будет немного иначе: $data = $cache->$objBla->data($dataName);
Во-вторых, debug_backtrace выдаст примерно следующее:
array
… // 0-й индекс нам, в данном случае, не интересен, хотя позже будет очень полезен 1 => array ‘file’ => string ‘путь_до_файла_в_котором_вызвали_кешер’ (length=54) ‘line’ => int 11 ‘function’ => string ‘data (исходный метод)’ (length=9) ‘class’ => string ‘objBla (исходный класс)’ (length=17) ‘type’ => string ‘->’ (length=2) ‘args’ => array 0 => &string ‘параметры исходного метода’ (length=11)
… // Тут много чего может быть
Возможно, ошибусь с индексами, но суть примерно такая.
Во-первых, на самом деле, что делает код $cache->$objBla->…: пытается обратиться к свойству объекта $cache, имя которого равно строковому представлению объекта $objBla. И получается Catchable fatal error: Object of class Foo1 could not be converted to string.
Во-вторых, вот это вы откуда взяли: ‘function’ => string ‘data (исходный метод)’ (length=9) ‘class’ => string ‘objBla ?
Вообще-то выше по стеку должна лежать функция fooBar, а class у неё и вовсе отсутствует по причине того, что она — не метод.
И нам из неё надо добыть локальныю переменную, но debug_backtrace даёт доступ только к аргументам, но не локальным переменным.
Ну и еще вы перепутали имя объекта с классом, поскольку objBla — имя переменной.
И даже если бы всё работало так, как вы себе представляете, знание имени пременной нам ничего не даст. Я вчера дал пример кода, который формирует массив имён, которые нужно отрезолвить из кэша вручную, чтобы проэмулировать вызов метода. Проблема в том, что нет способа получить самый первый объект этой последовательности: он лежит в локальном кадре стека вызвавшей нас функции.
Упс, сорри. Не проверил про локальные переменные =(.
Проверял на подобном:
Cacher::instance() -> Security::xss_clean(‘test string’);
Значит и этот метод отпадает… Осталось ReflectionClass проверить, но вряд ли тогда что-то поменяется. А тестить и проверять меня ломает, если честно.
Тормоз, у меня сумашедшая идея. А зачем нам вообще пытаться достучаться из кэша до объекта, если можно передать в него объект явно.
Допустим, было:
$data = $class->someMethod();
Стало:
$data = $cacher($class)->someMethod();
А он скобки не любит =)
Да-да, не люблю скобки. Подстановка фиговины с одной стороны и оборачивание скобками — это две большие разницы. И это непоследовательно: с функциями работаем так, с методами объектов сяк. Некрасиво.
Для чего точно нужен Reflection, я пока не понял, но постараюсь понять, спасибо.
Оке, Тормоз. Я посмотрю чего ты добьешься в решении этой задачи. Надеюсь твое решение будет более профессиональным.
Тормоз, тебе шашечки или ехать? Пиши тогда свой PHP, с блекджеком и без скобочек. >_<
«И это непоследовательно: с функциями работаем так, с методами объектов сяк.»
о_О
В каком месте не последовательно-то:
Было: $data = someFunction($blabla);
Стало: $data = $cacher()->someFunction($blabla);
Ты сам забыл что предлагал? Теперь тоже в формате было/стало но с методом объекта и сравни.
Ну и?
1) Вызываем функцию в глобальном прстранстве имён — не указываем в скобках объект.
2) Вызываем метод объекта — указываем соответствующий объект.
Всё последовательно, как синтаксически, так и логически.
Глобальную-то функцию мы отрезолвить можем просто вызвав её по имени, нам пляски с бубном вокруг передачи в кэш локального объекта не нужны в этом случае.
все равно не могу отоваться от ваших великов :)
@aktubа
debug_backtrace я бы не использовал, 1) медленно 2) его реально отключают на виртуальных хостингах
@jeck это мой первый код на пхп за последние 4 года, наверное. хотя нет, было дело, но так же, не больше пары сотен строк. увлекся так увлекся, мне очень не комфортно теперь с его типизацией и тэпэ, потому так.
я обо всем этом, кстати, писал, мол все плохо, вышло что вышло.
основная проблема мне тут видится в неразвитости хеширования объектов, массивов и скаляров в пхп
сам подумай, сколько будет отрабатывать getKey на реальных объектах и целесообразен ли вообще такой кеш
а то что оно работает, спору нет
так же как и мой вариант работает, достаточно только заменить spl_object_hash на свой вариант, привести все к объектам с общим предком и описать для всего хеширование, но тогда потеряется связь со встроенными типами. хотя все это вопрос времени, надеюсь скоро пых станет снова торт и еще покажет всем ;)
«не могу отоваться от ваших великов»
Нифига не понимаешь в колбасных обрезках — это не велики, а подготовка конкурсной работы на лучший code WTF года. ;)
Наверняка так и есть, просто Тормоз не сознаётся.
Если не победитель, то приз зрительских симпатий как минимум =)
Миша, а откуда такие данные про виртуальные хостинги? Что опасного в debug_backtrace?
Если без изменений кода в проекте, то поможет только runkit (https://github.com/zenovich/runkit/). Это модуль для PHP, так что придется пересобрать интерпретатор. Модуль позволяет переопределять встроенные PHP-функции, можно сделать свою обертку для функций вроде file_get_contents, которая будет лезть в кэш.
Ну не, это совсем неподходящее решение.