Блог веб-программиста

14Мар/130

Установка libssh2-php в Mac OS X

Для установки расширения ssh2 для php под маком, можно воспользоваться командой:

sudo pecl install channel://pecl.php.net/ssh2-0.12

Однако, как правило, она ругается нам следующим:

configure: error: The required libssh2 library was not found. You can obtain that package from http://sourceforge.net/projects/libssh2/
ERROR: `/private/tmp/pear/temp/ssh2/configure --with-ssh2' failed

Значит нам надо поставить libssh2 из портов MacPorts

sudo port install libssh2

Затем повторяем pecl install..., но когда нас спросят про libssh2 prefix, надо ввести /opt/local.

Всё. Перезапускаем apache и всё работает!

Связано с категорией: mac Нет комментариев
21Фев/132

NetPlant — простая хостинговая панель

В силу необходимости, решил написать свою хостинговую панель для shared хостинга.

Почему свою? Всё просто.

Ни одна из тех панелей, что я нашел, не умеет:

  • Работать с несколькими серверами из одной панели
  • Давать возможность создавать сайты со своими конфигами
  • Работать в конфигурации nginx+php-fpm, nginx+apache одновременно

Результат моего 2.5 дневного творения уже можно пощупать на github: https://github.com/DevGroup-ru/NetPlant.
Пока что умеет только создавать сайты на серверах из конфигов, но лично мне это уже хорошо упрощает жизнь.

Основано на:

  • Yii framework 1.x
  • Предыдущих наработках для движка CMS
  • Интерфейс - twitter bootstrap (via YiiBooster)

По поводу лицензии пока не задумывался - сначала надо довести до более-менее стабильной версии.

Связано с категорией: NetPlant 2 Комментарии
30Ноя/120

Обход ограничения max_input_vars в PHP без php.ini

Сегодня столкнулся с ограничением на количество входящих из формы в скрипт переменных в PHP.

Отвечает за это параметр max_input_vars, который по-умолчанию равен 1000.

Поскольку я разрабатываю CMS, то было бы не правильным в .htaccess менять это значение. Поэтому я решил обойти это.

С данной проблемой у меня сталкивается админка. А поскольку она не работает и не должна работать без JS, то я использовал такой JavaScript:

$("#editSaveButton").on('click', function(){
form = $("<form method=\"POST\"></form>")
.attr('action', $('#editForm').attr('action'))
.append(
$("<input type=\"hidden\" name=\"serializedData\">").val(
$("#editForm").serialize()
)
)
.append(
$("#editForm input[name=token]").clone() //@todo token name can be different
)
.appendTo($("body"))
.submit();
 
return false;
});

И вот такой код в PHP, чтобы заменить $_POST нужными данными:
 

	private function prepareSerializedData() {
		if (isset($_POST['serializedData'])) {
			$_POST = CMap::mergeArray($_POST, $this->parse_str($_POST['serializedData']));
			unset($_POST['serializedData']);
		}
	}
 
	private function parse_str($string) {
	    $parts = explode("&", $string);
	    $result = array();
	    foreach ($parts as $part) {
	    	$parsed = array();
	    	parse_str($part, $parsed);
	    	$result = array_merge_recursive($result, $parsed);
	    }
	    return $result;
	}
Связано с категорией: Code Нет комментариев
12Ноя/120

Яндекс сменил принцип расчета тИЦ?

Все мы знаем, что ТИЦ расчитывается как бы для сайта целиком, а не для страницы, как это делается в алгоритме PageRank.

Собственно в хелпе Яндекс так и пишет:

Наш тематический индекс цитирования (тИЦ) определяет «авторитетность» интернет-ресурсов с учетом качественной характеристики ссылок на них с других сайтов.

© http://help.yandex.ru/catalogue/?id=873431

Казалось бы, всё предельно понятно. Интернет-ресурс это сайт.

Но не тут то было. Сегодня внезапно случайно обнаружил, что Яндекс пытается мутить и определять на сайте разделы(ссылка "Все подразделы" в яке). Пока что, видимо, у него это не очень то получается:

Это собственно всё. Всего-то две категории. Что самое интересное - у этих категорий свой тиц.  И всё бы хорошо, для ленты вроде бы это более-менее логично.

Но вот, что творится, например, с facebook:

Странно? Более чем. Видимо это какой то глюк в алгоритмах.

 

Связано с категорией: SEO Нет комментариев
21Окт/120

Less.js и кеширование

Есть такой замечательный скрипт less.js, который позволяет компилировать LessCSS прямо в браузере. Скрипт поддерживает кеширование скомпонованного less средствами HTML5 Local storage.
Но есть одна неприятная особенность - подключенные через @include файлы не проверяются на изменения, что в свою очередь ведет к некоторым неудобствам при разработке.
Для очистки кеша я нашел на просторах интернета такой скрипт:

    function destroyLessCache(pathToCss) { // e.g. '/css/' or '/stylesheets/'
 
      var host = window.location.host;
      var protocol = window.location.protocol;
      var keyPrefix = protocol + '//' + host + pathToCss;
 
      for (var key in window.localStorage) {
        if (key.indexOf(keyPrefix) === 0) {
          delete window.localStorage[key];
        }
      }
    }

Собственно используя destroyLessCache("/bootstrap/less"); вместе с загрузкой страницы, мы сбрасываем весь кеш less.js.
В качестве параметра функция принимает путь к less файлам, относительно хоста.

Связано с категорией: Code Нет комментариев
17Окт/122

Локаль Ubuntu 12.04

Устанавливаем кошерную русскую локаль в Ubuntu из консоли:

locale-gen ru_RU.UTF-8

В /etc/default/locale пишем:

LANG="ru_RU.UTF-8"
LANGUAGE="ru_RU:en"
LC_NUMERIC="ru_RU.UTF-8"
LC_TIME="ru_RU.UTF-8"
LC_MONETARY="ru_RU.UTF-8"
LC_PAPER="ru_RU.UTF-8"
LC_IDENTIFICATION="ru_RU.UTF-8"
LC_NAME="ru_RU.UTF-8"
LC_ADDRESS="ru_RU.UTF-8"
LC_TELEPHONE="ru_RU.UTF-8"
LC_MEASUREMENT="ru_RU.UTF-8"

logout и вуаля! Русский язык на месте.

Связано с категорией: linux 2 Комментарии
2Окт/120

Настройка nginx для A/B тестирования с помощью lua

Возникла жесткая необходимость настройки сплит-тестирования (A/B тестирование) сайта.
Поскольку в моем случае разные версии сайта были даже на разных движках, то самым логичным было менять document root в зависимости от куки пользователя.
Но вот незадача - эту куку надо ещё как то установить. Делать для этого отдельный бекенд, как советует mixlr мне не хотелось.
Как установить рандомную куку средствами самого nginx, я так и не понял.
Решено было использовать сборку nginx - OpenResty, поскольку в ней уже встроен модуль HttpLuaModule.
Да, я знаю, что это извращение, но писать модуль для nginx на C не было никакого желания.

В результате получился вот такой вот скрипт на lua, который нужно вставить в нужное место в своём конфиге:

set $target '';
 
access_by_lua '
	local abtest = 0
	local abtestMax = 1
	local userAgent = ngx.req.get_headers()["User-Agent"]
 
	local cookie = ngx.var.cookie_abtest
	if ngx.re.match(userAgent, "(yandex|google|MSIE|bot)", "i") then
	    cookie = 0
	end
 
	if cookie == nil then
		math.randomseed( os.time() )
		abtest = math.random(0, abtestMax)
    		ngx.header["Set-Cookie"] = {"abtest=" .. abtest .. "; path=/", "abReason=nil"}
 
	else
 
		if tonumber(cookie) >= 0 and tonumber(cookie) <= abtestMax then
 
			abtest = cookie
 
		else
			abtest = 0
 
			ngx.header["Set-Cookie"] = {"abtest=" .. abtest .. "; path=/", "abReason=badRange"}
		end
 
	end
 
	ngx.var.target = abtest
';
 
root /var/www/meta$target;

Логика проста - скрипт смотрит, есть ли кука.
- если user-agent содержит yandex, google, MSIE или bot -- кука с тестом 0 -- это для того, чтобы поисковики и IE видели старую версию сайта(№0). Поисковики тем самым не обвинят нас в клоакинге.
- если кука nil, т.е. её нет - выбираем случайную куку от 0 до abtestMax и передаём её в браузер. Таким образом вероятность выпадения при abtestMax=1 - 50%/50%
- если кука не в нашем диапозое [0;abtestMax] - кука будет 0(версия сайта №0)
- если в диапозоне, то всё ок

Далее устанавливается переменная $target равная номеру теста и ниже уже ставится documentRoot в зависимости от неё.

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

Связано с категорией: linux, SEO Нет комментариев
9Авг/120

Необычное проявление ошибки failed to open stream: No such file or directory

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

Сегодня наткнулся на такой вот странный баг, проявившийся при разворачивании ранее рабочего сайта на другом сервере:

PHP Error [2]
include(EasmSelectEx.php): failed to open stream: No such file or directory (/Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/YiiBase.php:423)
#0 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/YiiBase.php(423): DotPlantWebApplication->handleError()
#1 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/YiiBase.php(423): autoload()
#2 unknown(0): autoload()
#3 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/web/CWidgetFactory.php(148): spl_autoload_call()
#4 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/web/CBaseController.php(147): CWidgetFactory->createWidget()
#5 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/web/CBaseController.php(173): AsmSelect->createWidget()
#6 /Users/bethrezen/Documents/DotPlant-private/protected/modules/User/widgets/AsmSelect.php(75): AsmSelect->widget()

Казалось бы проблема в отсутствующем файле, но это не так. Файл присутствует и права на месте.

Проблема же была в том, что в этом файле использовались short tags - <? вместо <?php.

В общем, странно, непредсказуемо, но логично. Ещё раз доказывает, что короткие теги не нужны.

Связано с категорией: Code Нет комментариев
28Апр/120

Yii: CStatRelation и defaultScope у реляций

Ночью столкнулся с внезапной проблемой в Yii Framework 1.
Оказывается, статистические запросы через CStatRelation не поддерживает scopes в параметрах. Так что, если мы имеем defaultScope в нашем связанно элементе, то отменить его действие мы не сможем.

Как повторить эту проблему. Допустим имеем класс RevisionsSets с таким вот определением реляций:

        /**
	 * @return array relational rules.
	 */
	public function relations()
	{
		return array(
			'pricesCount'=>array(
					self::STAT, 'Revisions', 'revisionSetId',
				),
		);
	}

Ну и в самом классе Revisions имеем следующую группу условий по-умолчанию:

        public function defaultScope() {
		$rev = Config::model()->getConfigValue("Shop", "CurrentRevision")->value;
		$t = $this->getTableAlias(false, false);
 
		return array(
					'condition' => "$t.revisionSetId=:rev",
					'params' => array('rev'=>$rev),
				);
 
	}

Что же происходит при вызове $revisionsSetModel->pricesCount?
STAT-реляция забирает количество записей, но при этом применяет defaultScope от модели Revisions и в итоге для всех экземпляров RevisionsSets считается некорректное количество записей.
Исправить такое поведение можно было бы допливанием CStatRelation и реализацией там поддержки условий, как это например сделано в HAS_MANY, MANY_MANY, BELONGS_TO.

Но мне в голову пришел более быстрый вариант. Я создал класс RevisionsResetScope, который наследует Revisions и переопределяет defaultScope:

<?php
 
class RevisionsResetScope extends Revisions {
	public function defaultScope() {
		return $this->resetScope();
	}
}

Этот класс и используем в реляции:

        /**
	 * @return array relational rules.
	 */
	public function relations()
	{
		return array(
			'pricesCount'=>array(
					self::STAT, 'RevisionsResetScope', 'revisionSetId',
				),
		);
	}

Вот такой костыль. Кстати, на форуме Qiang Xue решил отказаться от статистических запросов и CStatRelation в Yii Framework 2. Будем надеяться, что разработчики предусмотрят достойную замену.

Связано с категорией: Code Нет комментариев
27Авг/110

Бесконечная очередь и отказ от сообщений в RabbitMQ + Thumper + PHP AMPQlib

По работе столкнулся с одной задачей и решил использовать сервер очереди сообщений RabbitMQ в связке с PHP 5.3 через PHP-ampqlib и библиотеку Thumper.

Чтобы сделать Consumer(worker), который будет обрабатывать бесконечную очередь надо задать $consumer->consume(-1);. Тогда в цикле Thumper, где проверяется нужно ли нам выходить всё будет хорошо и наш обработчик будет работать вечно.

Но появился ещё один интересный вопрос - что делать, если внутри функции обработки сообщения произошел Exception? Я решил, что тогда мы отказываемся от этого сообщение и оно идет к другому обработчику. Реализовывать это лучше расширением класса Consumer:

class MyConsumer extends Consumer {
	public function processMessage($msg) {
		try {
			parent::processMessage($msg);
		} catch (Exception $e) {
			echo "Message rejected due to exception.\n".$e->getMessage()."\n";
			$msg->delivery_info['channel']->basic_reject($msg->delivery_info['delivery_tag'],true);
			//throw $e;
		}
	}
}

Соответственно ваш Consumer должен быть объектом класса MyConsumer.
Если же всё таки нужно кинуть этот Exception выше - убираем комментарий со строки throw $e;

Связано с категорией: Code Нет комментариев
Предложения для дилеров: одноразовая бумажная посуда, Promaster.