Как обычно, в анекдоте - ровно трое в самолете.
Самолет к такой-то тете, как положено, упал.
Всё - как надо, все живые, и веселые такие,
В общем, каждый, улыбаясь, к людоедам в плен попал.
Hо сначала долго бились и, конечно, утомились,
Людоеды навалились, - ну какой тут паритет!
"Все, хана вам, оккупанты!" - заорал на эсперанто
С толстой мордой интенданта самый хищный людоед.
И к вождю их потащили - по пути почти не били, -
Hо ребята приуныли - дело дрянь - пиковый туз...
Было ясно и понятно - станут жрать, причем бесплатно,
Так попали в "неприятно" русский, янки и француз.
И сказал им вождь: "А ну-ка, разгоните нашу скуку,
Hазовите, братцы, штуку, чтоб у нас и не сыскать.
Говорите, будем слушать, кто промажет - станем кушать,
Hу а кто натрет нам уши - что ж, придется отпускать."
И француз без подготовки бахнул, словно из винтовки:
"Миль пардон, но ваши телки все страшнее обезьян,
Приведите мне мулатку, чтоб вкуснее шоколадки,
Что, забило сердце в пятку!" (Hу, француз, ну, хулиган!)
И заржали тут бандюги, посинели от натуги,
Подал знак своей прислуге вождь в банановом плаще.
Людоеды побежали, привели такую лялю -
Кто увидел, тот едва ли возразил бы вообще.
Тут французу стало грустно, стал кричать, что он невкусный,
А потом сказал, что гнусно так с гостями поступать.
А его не стали слушать, обещали завтра скушать
И, не разделывая тушу, стали перцем натирать.
Жаль француза, с ним все ясно - да, она была прекрасна
И поставил Жан напрасно жизнь свою на женский пол.
Hо теперь попытка Джона, уроженца Аризоны,
(Hеужели, как бизона, тоже подадут на стол?)
Джон воскликнул "Спору нету. Убирайте девку эту,
И подайте мне ракету - наш последний образец!"
Людоеды завопили, в барабан заколотили
И из джунглей прикатили Шатл - крылатый огурец.
Что поделаешь - финита! Карта брошена и бита!
Hалетели, как москиты, стали солью посыпать.
Да, попали в положенье, что тут скажешь в утешенье?
А Иван нашел решенье: "Слушай вождь, ядрена мать!
Прикажи своим придуркам петуха, аль, скажем, курку
Положить башкой на чурку и кончать, и ощипать,
Два часа варить и парить и до корочки обжарить, -
Вот тогда начнем гутарить и задачки задавать!"
И петух зажарен жирный, плыл по джунглям запах мирный,
И сидел Ванюша смирно, вел беседу у огня.
Как в гостях у тети Груни, дикари роняли слюни...
"Все, готов? Теперь пусть клюнет там, где ж... копчик у меня!"
...Говорят, что смех полезен. Вождь смеется, вождь любезен,
Ваня тоже аж до рези в животе поймал "ха-ха".
Тут колдун подходит местный и в момент (о царь небесный!),
Заклинаньем неизвестным оживляет петуха.
Встал петух такой хрустящий, жареный и настоящий,
Клюнул Ваню в тыл изящно, но чувствительно, подлец!
Дикари, как дети, рады, колдуну петух - в награду.
Обдурили, значит, гады, тут уж шуточкам конец...
Что теперь тянуть резину - подхватил Иван дубину
Перебил всех, как скотину, Жана с Джоном развязал.
И спросил американец: "Что ж ты сразу этих пьяниц
Hе угробил разом, Ваня? Целый день у нас пропал".
"Что ж, друзья, скажу, не скрою: просто русский так устроен.
И пока его не клюнет в ж... копчик жареный петух,
Дремлет в нем великий воин, до поры, как грязь, спокоен
Потому что, в общем, добрый в русских людях русский дух".
Just wanted to share something I'm using on a daily basis...
We all know that external APIs sometimes can be slow and unreliable. It's quite annoying to guard any call to an external API with try...catch block so here's my PHP 5.3 only "solution" to this problem... Obviously if external service is down it's not going to magically fix it but it's going to retry a few times before returning false.
Here's an example:
$response = null;
$success = retry(3, function() use ($api, &$response) {
$response = $api->getSomething();
});
I think it's pretty obvious what this function does. Usually your external service handling class will throw an exception in case HTTP connection has timed out or reponse it's received is not valid. retry will catch that exception (or it can only catch exceptions of specified types) and will retry again after a certain delay (you can specify how many times you want it to retry before givinig up).
Here's the retry function itself:
/**
* Calls the callback function and in the case if exception is thrown retries
* a given number of times. Returns true on success.
*
* @param numeric $retries
* @param callback $callback
* @param array $exceptions
* @return bool
*/
function retry($retries, $callback, array $exceptions = null) {
if (!is_callable($callback)) {
throw new Bi_Exception('Argument 2 must be a valid callback');
}
$sleep = 1;
while ($retries--) {
try {
$callback();
return true;
}
catch (Exception $e) {
sleep($sleep);
$sleep *= 2;
}
}
if (is_array($exceptions)) {
$should_throw = true;
foreach ($exceptions as $exception_type) {
if (is_a($e, $exception_type) || is_subclass_of($e, $exception_type)) {
$should_throw = false;
break;
}
}
if ($should_throw) {
throw $e;
}
}
return false;
}
This class could be helful in case you have no error/exception handling code and believe you have absolute test coverage for every single component of your site ;) Just add Ns_ErrorHandler::setHandlers() to your bootrap file and who knows, maybe there's actually something you've missed.
class Ns_ErrorHandler
{
/**
* @var array
*/
private static $_ignoredErrors = array(8, 2048, 8192, 16384); // E_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED
/**
* @var array
*/
private static $_fatalErrors = array(1, 256); // E_ERROR, E_USER_ERROR
/**
* Sets error handlers
*/
public static function setHandlers()
{
set_error_handler(array(__CLASS__, 'handleError'));
set_exception_handler(array(__CLASS__, 'handleException'));
register_shutdown_function(array(__CLASS__, 'shutdown'));
}
/**
* Handles PHP errors
*
* @param int $errno
* @param string $error
* @param string $errfile
* @param string $errline
*/
public static function handleError($errno, $errstr, $errfile, $errline)
{
// Ignore error?
if (in_array($errno, self::$_ignoredErrors)) {
return;
}
// Send email
$errorString = "{$errno}: {$errstr} in {$errfile} on line {$errline}";
mail('notify@this.address', 'Error', $errorString);
// Display eror page
if (in_array($errno, self::$_fatalErrors)) {
@ob_end_clean();
self::_showErrorPage();
}
}
/**
* Handles Exception
*
* @param Exception $e
*/
public static function handleException(Exception $e)
{
// Send email
mail('notify@this.address', 'Error', print_r($e, true));
// Display erro page
@ob_end_clean();
self::_showErrorPage();
}
/**
* Using shutdown function we can catch fatal errors
* which don't go through error_handler
*/
public static function shutdown()
{
if ($err = error_get_last()) {
call_user_func_array(array(__CLASS__, 'handleError'), array_values($values));
}
}
private static function _showErrorPage()
{
// show some html here
}
}
// Set handlers
Ns_ErrorHandler::setHandlers();
Questions?
На днях на работе мне было поручено написать очень простенький crawler который будет находить ссылки в разных источниках (к примеру Twitter) и будет эти ссылки нам сохранять. Само собой всякие укоротители ссылок вроде bit.ly нас не интересуют, нам нужны конечные ссылки и желательно titles страниц на которые ссылки указывают.
Сначала казалось что никаких проблем возникнуть не должно. У меня был тестовый list на твиттере где появлялось в среднем до 10 ссылок в минуту. Логично было написать скрипт, запускаемый cron-ом каждые 10-15 минут, который будует получать новые tweets в этом листе и будет их сканировать на наличие ссылок. Сказано - сделано, скрипт написан, cron подправлен.
class Crawler
{
public function crawl(array $lists)
{
foreach ($lists as $list) {
$tweets = $twitterClient->getLatestTweets( $list );
// Collect urls first
$urls = array();
foreach ($tweets as $tweet) {
$urls += $this->_extractUrls($tweet['text']);
}
// Now analyze them
foreach ($urls as $url) {
list($final_url, $title) = $this->_analyzeUrl($url);
}
}
}
private function _analyzeUrl($url)
{
list($url, $body) = $http_client->_analyze($url);
$title = $this->_extractPageTitle($body);
return array($url, $title);
}
}
$crawler = new Crawler;
$crawler->crawl($list);
Однако когда я получил настоящий список twitter аккаунтов (а их оказалось 50) подобный метод оказался слишком медленным. В среднем анализ одной ссылки занимал 1-1.5 секунды. Итого 50 аккунтов * в среднем 30 ссылок в каждом (если запускать скрипт каждые 10 минут) - получаем 1500 ссылок которые надо проверить. Итого ~30-35 минут. Абсолютно неприемлемо.
Используя pcntl extension мы получаем возможность fork наш процесс столько раз сколько мы хотим параллельно работающих процессов. Однако есть одна проблема, между старшим и дочерним процессом нет никакой связи. Первая версия этого класса использовала временные файлы. Поскольку старший процесс знает PID дочерних процессов мы можем периодически проверять появился ли файл допустим с названием /tmp/crawler_result.[PID]. Как только файл появился и его содержимое валидно (я использовал набор символов в конце файла) мы знаем что дочерний процесс закончил работу и мы можем его освободить и запустить следующий.
Однако следующий вариант мне нравится еще больше поскольку операции чтения-записи файлов будут помедленнее чем прямой доступ к памяти. Итак здесь мы спользуем еще два extension - sysvshm и sysvsem. Немного теории. Нам нужен кусок памяти доступный из старшего и дочерних процессов. Однако чтобы избежать повреждения информации нам нужно сделать так чтобы только один процесс мог записывать в определённое время. Для этого мы будем использовать семафор. Смотрим код и постигаем! :)
class Crawler
{
private $_runningThreads = array();
private $_concurrentThreadsNum = 10;
public function __construct()
{
// Setup semaphore and shared memory
$token = ftok(__FILE__, 'c');
$this->_semaphore = sem_get($token);
if (! $this->_shm = @shm_attach($token, 1 * 1024 * 1024, 0644)) {
echo 'Unable to allocate memory (1 megabyte)';
exit;
}
}
public function __destruct()
{
if ($this->_parentProcess) {
shm_remove($this->_shm);
shm_detach($this->_shm);
}
}
public function crawl(array $lists)
{
foreach ($lists as $list) {
$tweets = $twitterClient->getLatestTweets( $list );
// Collect urls first
$urls = array();
foreach ($tweets as $tweet) {
$urls += $this->_extractUrls($tweet['text']);
}
// Analyze forked
$this->_analyzeForked($urls);
}
}
private function _analyzeForked(array $urls)
{
$this->_urls = $urls;
$this->_parentProcess = false;
$current_index = 0;
$urls_count = count($urls);
while (true) {
// Harvest zombies
foreach ($this->_runningThreads as $i => $threadId) {
if (pcntl_waitpid($threadId, $status, WNOHANG) > 0) {
unset($this->_runningThreads[$i]);
}
}
// Run some more threads
if ($current_index < $urls_count) {
// Run more threads if there are empty slots
if (count($this->_runningThreads) < $this->_concurrentThreads) {
// Spawn thread
if ($pid = $this->_fork($current_index)) {
$this->_runningThreads[] = $pid;
}
// Fallback in case spawning thread has failed
else {
$analysed_data = $this->_analyzeUrl($urls[$current_index]);
$this->_updateData($current_index, $analysed_data);
}
$current_index++;
}
}
// Are all threads done?
if (!$this->_runningThreads) {
$this->_parentProcess = true;
break;
}
}
// Final data
return $this->_getData();
}
/**
* Fork this process
*
* @param int $index
*/
private function _fork($index)
{
$thread_id = pcntl_fork();
// Couldn't spawn child process
if ($thread_id == -1) {
return false;
}
// Child spawned, save the process id
elseif ($thread_id) {
return $thread_id;
}
// Update data
$analysed_data = $this->_analyzeUrl($this->_urls[$index]);
$this->_updateData($index, $analysed_data);
exit;
}
/**
* Analyze url
*
* @param string $url
* @return array
*/
private function _analyzeUrl($url)
{
list($url, $body) = $http_client->_analyze($url);
$title = $this->_extractPageTitle($body);
return array($url, $title);
}
/**
* Safely updated data in the shared memory block
*
* @param int $index
* @param mixed $_data
*/
private function _updateData($index, $_data)
{
sem_acquire($this->_semaphore);
$data = shm_has_var($this->_shm, $this->_shmId) ?
shm_get_var($this->_shm, $this->_shmId) :
array();
$data[$index] = $_data;
shm_put_var($this->_shm, $this->_shmId, $data);
sem_release($this->_semaphore);
}
/**
* Return data currently stored in shared memory block
*
* @return array
*/
private function _getData()
{
if (shm_has_var($this->_shm, $this->_shmId)) {
sem_acquire($this->_semaphore);
$data = shm_get_var($this->_shm, $this->_shmId);
sem_release($this->_semaphore);
return $data;
}
return array();
}
}
$crawler = new Crawler;
$crawler->crawl($list);
Не стесняемся, задаём вопросы.
http://pleac.sourceforge.net/pleac_php/processmanagementetc.html
Че-то надоело мне мучаться с ссылками в Smarty и я, как говорится, пошел другим путём :) Напишем маленький скриптик который будет для каждого зарегистрированного пути создавать функцию для смарти, которую мы потом сможем вызывать для генерирования полноценного URL:
$routesFunctions = '/some_path_where_you_want_this/smarty.url_plugins.php';
$fileContents = '<?php ' . PHP_EOL;
foreach ($router->getRoutes() as $routeName => $routeParams) {
$methodName = "{$routeName}_url";
$fileContents .= <<<HEREDOC
function smarty_function_{$methodName}(\$params, \$smarty) {
\$params = array_merge(\$params, array('name' => '{$routeName}'));
return smarty_function_url_for(\$params, \$smarty);
}
HEREDOC;
}
file_put_contents( $routesFunctions, $fileContents );
И теперь при условии что у нас вот такой вот простенький список routes...
$routes = array(
'login' => array(
'route' => '/login',
'defaults' => array(
'controller' => 'auth',
'action' => 'login'
)
),
'show_blog_post' => array(
'route' => '/blog/posts/:id/show',
'defaults' => array(
'module' => 'blog',
'controller' => 'posts',
'action' => 'show'
)
)
);
... в шаблоне мы можем сделать следующее:
<a href="{login_url}">Login</a>
либо
<a href={show_blog_post id=$post->id}">Show this blog post</a>
Само собой, при каждом изменении файла с путями прийдётся заново генерировать файлик с функциями, но думается мне, что это не большая проблема. Кстати вот пример того как может выглядеть функция url_for, которая вызывается внутри сгенерированых функций.
function smarty_function_url_for($params, $smarty)
{
// Надо где-то взять urlHelper :)
global $urlHelper;
if (!$routeName = $params['name']) {
throw new Exception('Route name must be given');
}
unset($params['name']);
if (array_key_exists('params', $params)) {
$params = $params['params'];
if (!is_array($params)) {
$params = array();
}
}
$routeDetails = Zend_Controller_Front::getInstance()->getRouter()->getRoute( $routeName );
$routeVars = $routeDetails->getVariables();
// Required vars to generate URL
$urlVars = array();
foreach ($params as $name => $val) {
if (in_array($name, $routeVars)) {
$urlVars[ $name ] = $val;
unset( $params[ $name ] );
}
}
$url = $urlHelper->url($urlVars, $routeName);
if (!count($params)) {
return $url;
}
// Query vars
$queryVars = array();
foreach ($params as $key => $var) {
$queryVars[] = $key. '=' . urlencode($var);
}
$url .= '?' . implode('&', $queryVars);
return $url;
}
И да, главное не забыть где-нибудь в bootstrap.php:
require '/some_path_where_you_want_this/smarty.url_plugins.php';
Зашел я сегодня в очередной раз на mail.ru почитать новости в разделе хайтек и мне на глаза попалась статейка на тему OSX vs Windows. В ней шла речь о том, что Windows 7 не будет толком работать на Маке потому, что Bootcamp драйвера для неё (W7) Apple еще не написали. Хотя со своего опыта знаю, что всё работает прекрасно. Поставил семёрку на свой Mac Mini и сразу были и видео, и звук. Но не о том речь. Речь сейчас пойдёт о непримиримой войне между кланами Win, OSX и Linux, которая неминуемо начинается в коментариях к таким статьям.
Я всегда задавался вопросом почему человек не может ценить плюсы и конечно же видеть минусы всех трёх. Однако горячие головы, фанаты рвут и мечут доказывая очевиднейшее превосходство своей операционки над всеми остальными. Фанаты виндоуса доказывают что Мак для тупых, мотивируя недостатком кнопок в панели управления. Фанаты Мака же более лояльны. Мне кажется, что они просто гордятся тем, что у них Маки и не пытаются особо ругать Windows (сами ведь недавно пересели с ХР на OSX) однако этот лёгкий снобизм, свойственный Маковца, как красная тряпка для быка (пользователя Win) и они, рвя на себе футболки и брызжа слюной, ещё более страстно начинают поливать Мак грязью. Точка зрения линуксоидов мне до сих пор не ясна. Возможно кто-то всё-таки сможет перечислить мне те причины по которым имеет смысл пересесть с Windows на ту же Ubuntu.
Но пока я бы хотел озвучить свои за и против в пользу всех трёх.
Windows я люблю за его гибкость и незащищённость. Я имею в виду, что при желании я могу угробить быстродействие компьютеры отключив Swap, могу удалить драйвера видеокарты и наслаждаться 16 цветами в потрясающем разрешении 640x480. Могу на худой конец удалить explorer.exe и угробить тем самым операционку в ноль. Мне это нравится. И думаю я не один, кто это ценит.
То что большинство програм выпускается именно для этой операционной системы мне даже не стоит упоминать.
Как ни странно, но не люблю я Windows по тем же причинам потому как мне еще приходится приглядывать за ноутбуками сестры и родителей. И в их неумелых руках всё вышеперечисленное становится поистине смертоносным орудием. Потому как любопытство - черта характера присущая всем без исключения представителям homo sapiens и потому не раз мной были замечены попытки посмотреть к чему приведёт изменение той или иной галочки в панели управления.
Вирусы. Стоит ли начинать? Не качать и не запускать с инета всё подряд? Установить антивирус?
...
Простота. Многие пользователи, особенно домохозяйки, пожилые люди, люди которые используют компьютер исключительно для работы да и порой бывалые матёрые сисадмины хотят чтобы компьютер просто работал. Чтобы его можно было включить, запустить браузер, полазить по Facebook, посмотреть фильм, послушать музыку и выключить. OSX является именно такой операционной системой. Достаточно купить любой компьютер Apple и забыть о всех вирусах и иных трудностях повседневной жизни (aka BSOD). За всё время пользования OSX я считаные разы видел их аналог BSOD да и то это было связано с железными проблемами.
...
Как по мне Linux это серверная операционная система. Компьютер без GUI наверняка работает побыстрее компьютера с оной поэтому в сфере серверов Linux является неоспоримым лидером. Веб серверы, серверы баз данных, игровые серверы. О да, всё это прекрасно работает и на Windows и на OSX но быстрее всего работает это на Linux.
...
Просто, чтобы подвести итог, мне хочется сказать, что все три решения хороши. Что наличие здоровой конкуренции заставляет компании работать непокладая рук над операционками. Это даст возможность рациональному человеку использовать ту операционную систему которая наиболее подходит под его потребности.
Хотелось бы еще сказать, что я использую все три операционки ежедневно и являюсь фанатом всех трёх. На работе у меня Macbook Pro, дома у меня Dell Studio Hybrid с Windows 7, а сайт работает на FreeBSD 7.2 на древнейшем P3 1GHz и должен сказать что работает хорошо.
Хочется дурой набитою стать,
Чтоб не уметь ни писать, ни читать,
Чтобы валяться круглые сутки...
Чтобы смеяться на глупые шутки...
Чтобы переться от розовой шмотки,
Чтобы подруги - одни идиотки,
Чтоб в ридикюле духи и жЫвачка,
Чтоб Петросян насмешил до усрачки.
Чтобы компьютер - большой калькулятор,
Чтобы с ашипкой писать <гиниратор>,
Чтобы Дом2 - <зашибись передача>,
Кучу любовников и побогаче.
Чтобы в наушниках - <Шпильки> с Биланом,
Чтобы трусы - только <Дольче Габана>,
Чтоб <кибернетика> - страшное слово,
Чтобы <политика - это не клево>.
В общем, хочу быть набитою дурой,
Брать не умом, а лицом и фигурой,
Все достигать, обнажая коленки...
Стать бы такой... и убица ап стенку
Станешь такой - офигеешь от скуки!
Будут вокруг не подруги, а суки.
Все мужики будут гады и жмоты,
Отдых достанет ну просто до рвоты.
Будут в квартире не стены - застенки.
Будут скучать друг по другу коленки.
Так что ресницами глупо не хлопай.
Взгляд в монитор и работай-работай!
Хорошие девочки лишаются девственности в первую брачную ночь, плохие - при первом удобном случае, умные - два-три раза;
Хорошие девочки лишаются девственности с мужем, плохие - с пьяным одноклассником, умные - с фаллоимитатором;
Хорошие девочки дают по любви, плохие - за деньги, умные - когда сами захотят;
Хорошие девочки читают сказки, плохие - Камасутру, умные - медицинскую энциклопедию;
Хорошие девочки верят мужчинам, плохие - не верят мужчинам, умные не верят никому;
Хорошие девочки от огорчения плачут, плохие - пьют, умные отправляются по магазинам;
Хорошие девочки комплексуют по поводу размера груди, плохие - закачивают силикон, умные гордятся тем, что имеют;
Хорошие девочки вышивают крестиком, плохие - колбасятся на дискотеках, умные - рыщут в Интернете;
Хорошие девочки умеют готовить, плохие - умеют сделать заказ, умные - сидят на диете;
Хорошие девочки владеют иностранными языками, плохие - матерным, умные - своим собственным;
Хорошие девочки относятся к сексу как к обязанности, плохие - как к развлечению, умные - как к части жизни;
Хорошие девочки берут в кино попкорн, плохие - презерватив, умные - губную помаду.
Хорошие девочки верят в рай, плохие - в ад, умные - в Дарвина;
Хорошие девочки ждут принца на белом коне, плохие - миллионера на черном “Мерседесе”, умные считают обоих персонажей вымышленными;
Хорошие девочки спят в пижаме, плохие - голыми, умные - по ситуации;
Хорошие девочки верны мужу, плохие - любовнику, умные - обоим;
Хорошие девочки загорают в купальнике, плохие - топлесс, умные - в тени,
Хорошие девочки верят в чистую любовь, плохие - в частую, умные - в качественную;
Хорошие девочки одеваются аккуратно, плохие - вызывающе, умные - быстро;
Хорошие девочки становятся заботливыми женами, плохие - феерическими любовницами, умные - верными друзьями