Friday, November 14, 2008

Эмоции

Завал и на работе и дома.

Пока второй день прут эмоции.

twisted - жжот!!!!

Tuesday, October 28, 2008

PHP, Excetion облегчают обработку форм. Ностальгия блин.

Теряя время в интернете на поиск интересных блогов связанных или с php или с веб программированием так, чтобы я мог увидеть в этом что то интересное для себя, набрел на блог http://alt-f4.ru. Поковырял статьи, особо сказать ничего не могу, кто-то делиться своим опытом, просто времена когда мне бы его опыт пригодился бы остались в прошлом.

В итоге набрел на статью "Авторизация посетителей на PHP", в которой по шагам, потихонечку написано как кому чего делать чтобы у него было счастье.

Ну а теперь самое интересное, наткнулся на код обработки формы.


if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$rLogin = trim($_POST['rLogin']);
$rPass = trim($_POST['rPass']);
$rPass2 = trim($_POST['rPass2']);
$rEmail = trim($_POST['rEmail']);
if ($rLogin == '') {
die("Поле 'Логин' не заполнено\n");
// Логин может состоять из букв, цифр и подчеркивания
}elseif (!preg_match("/^\w{3,}$/", $rLogin)) {
die("В поле 'Логин' введены недопустимые символы\n");
}
if ($rEmail == '') {
die("Поле 'E-mail' не заполнено\n");
// Проверяем e-mail на корректность
}elseif (!preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}$/", $rEmail)) {
die("Указанный 'E-mail' имеет недопустимый формат\n");
}
if ($rPass == '' || $rPass2 == '') {
die("Поле 'Пароль' не заполнено\n");
}elseif($rPass !== $rPass2) {
die("Поля 'Пароль' и 'Повтор пароля' не совпадают\n");
// Пароль может состоять из букв, цифр и подчеркивания
}elseif(!preg_match("/^\w{3,}$/", $rPass)) {
die("В поле 'Пароль' введены недопустимые символы\n");
}
// В базе данных у нас будет храниться md5-хеш пароля
$mdPassword = md5($rPass);
// А также временная метка (зачем - позже)
$time = time();
// Устанавливаем соединение с бд(не забудьте подставить ваши значения сервер-логин-пароль)
$link = mysql_connect('localhost', $dbuser, $dbpass);
if (!$link) {
die("Не могу соединиться с базой данных");
}else {
// Выбираем базу данных
mysql_select_db('authorize', $link);
// Записываем в базу (не используем addslashes - экранировать нечего)
mysql_query("INSERT INTO users (login, pass, email, timestamp)
VALUES ('$rLogin','$mdPassword','$rEmail',$time)",$link);
if (mysql_error($link) != "") {
die("Пользователь с таким логином уже существует, выберите другой\n");
}
echo "Юзер добавлен\n";
mysql_close($link);
}
}
?>


В общем, про использование прямых запросов сразу молчим, но что наглядно, форма из 4-х полей, а уже 3 вложенных условия, да и вообще логика не простая.

Самое что главное, если заменить способы валидации, то к своему стыду получается что я писал подобный код не так давно. И только натолкнувшись на совсем сумасшедшую логику, прикрутил Exceptions и чудо
1. Объем кода уменьшился в два раза.
2. Количество вложенных условий сократилось до 1-го
3. Ошибки стало проще выводить.

Ну и применительно к данному коду, применя преобразовния не меняющие логику получим что то вроде. .


class DbException extends Exception {};

if ($_SERVER['REQUEST_METHOD'] != 'POST')
{
try
{
foreach ( array('rLogin' => 'Логин' , 'rPass' => 'Пароль' , 'rPass2' => 'Повторный пароль', 'rEmail' => 'E-mail' ) as $val => $fieldName)
{
if ( isset($_POST[$val] ) )
$val = trim($_POST[$val]); //create rLogin, rPass, rPass2, rEmail variables
else
throw new Exception("Поле $fieldName не заполнено\n");
}

if (!preg_match("/^\w{3,}$/", $rLogin))
throw new Exception("В поле 'Логин' введены недопустимые символы\n");

if (!preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}$/", $rEmail))
throw new Exception("В поле 'Логин' введены недопустимые символы\n");

if($rPass !== $rPass2)
throw new Exception("Поля 'Пароль' и 'Повтор пароля' не совпадаютn");

if(!preg_match("/^\w{3,}$/", $rPass))
throw new Exception("В поле 'Пароль' введены недопустимые символы\n");

$mdPassword = md5($rPass);
// А также временная метка (зачем - позже)
$time = time();
// Устанавливаем соединение с бд(не забудьте подставить ваши значения сервер-логин-пароль)
$link = mysql_connect('localhost', $dbuser, $dbpass);
if (!$link)
throw new DbException("Не могу соединиться с базой данных");
// Выбираем базу данных
mysql_select_db('authorize', $link);
// Записываем в базу (не используем addslashes - экранировать нечего)
mysql_query("INSERT INTO users (login, pass, email, timestamp) VALUES ('$rLogin','$mdPassword','$rEmail',$time)",$link);// поле timestamp можно сделать чтобы по умолчанию подставлялось базой, такие вещи лучше перекладывать на базу.
if (mysql_error($link) != "")
DbException("Пользователь с таким логином уже существует, выберите другой\n");
echo "Юзер добавлен\n";
mysql_close($link);
}
catch (DbException $e)
{
die("DB ERROR: " . $e->getMEssage());
}
catch ( Exception $e)
{
die( "Form validation error: ". $e->getMessage());
}
}
?>

В итоге.

  • Кода меньше.

  • Читается проще.

  • Вывод ошибок в одном месте.

  • При необходимости разные виды ошибок ловим в нужных нам местах.



Monday, October 27, 2008

Как я люблю php, или танцы с false

Еще одна из кривостей моего любимого php. Писать не буду, приведу пример интерактивного шела.
Cюрприз #2

pcd@cd-laptop:~$ php -a
php > error_reporting(E_ALL);// устанавливаем уровень ошибок,

php > $ar = array(); // создаем массив
php > var_dump($ar[1]); // обращаемся к несуществующему индексу
Notice: Undefined offset: 1 in php shell code on line 1//логично
NULL

php > $null = null;// создаем переменну инициализированную нулом
php > var_dump($null[1]);// обращаемся к нему как к массиву
NULL// ошибки нет, это уже не совсем логично, т.к.

php > var_dump( (array) $null ) ; // при преобразовании нула в массив
array(0) {
}// получается пустой массив без индекса 1.
//ну хотя можно как то объяснить, т.к. null это особый тип.

php > $false = false;// boolean переменная, инициализированная ложью
php > var_dump($false[1]);// обращение как к массиву при этом к несуществующиему индексу
NULL// а как объяснить это????

php > var_dump((array)$false);// смотрим как ложь приводится к массиву.
array(1) {
[0]=>
bool(false)
}// этого я не ожидал
// но тогда получается что ...

php > echo (array) false ? 'true' : 'false';
true // ну а это просто п***ц, сколько человек могло попасть так!


Ну и навскидку надуманный пример, похожий на правду.



cd@cd-laptop:~$ cat ex1.php
class ExampleDb
{

/**
* This function return list of users as array,
* empty array on failure
*/
public function findAll($testValue = true)
{
return (array ) $this->_fetch_all($testValue);
}
protected function _fetch_all($testValue = true )
{
$goodData = array(
0 => array(
'name' => 'vasya',
'pass' => 'oyamd5withsolt',
),
1 => array(
'name' => 'vasay',
'pass' => 'oyamd5withsolt2',
),
);
try
{
;//do smth
}
catch (Exception $e)
{
return false; //strange architector
}

$retValue = $testValue ? $goodData : false;
var_dump($retValue);
return $retValue;

}
}

$ob = new ExampleDb();
$users = $ob->findAll(false);// error happens !

//strange dev
if ( isset($users[0]) )
echo "You win! Your prize 10000k euros from dev salary!";


?>
cd@cd-laptop:~$ php ex1.php
(bool)false // мы видим что была ошибка
You win! Your prize 10000k euros from dev salary!cd@cd-laptop:~$
// но тем не менее кто то выйграл.
//Ну что теперь 10 лет работаем на еду?

Sunday, October 26, 2008

Update старых записей.

Обновил пост о создании макроса в trac. (Чуть-чуть причесал код, заменив формирование html вместо строк теперь использую genshi.builder.tag

Также настройки доступа к базе, скрипт получает теперь из конфига трака.


Обновил пост об авторизации по ssh без пароля, оказывается некоторые вещи которые я делал руками делаются одной командой :)

Friday, October 24, 2008

PHP, PDO, веселье при наследовании и включенной опции persistent connect

За что я люблю PHP?

За то что это глобально и надежно. Ну какой еще другой язык может мне в пятницу утром сломать мозг.

Этой статьей открою растянутый список статей в которых опишу подводные камни удобно предоставленные PHP нашей команде.

Cюрприз #1

В проекте есть класс DB, отнаследованный от PDO. Было обнаружено неадекватное поведение объектов. 3 часа ломания мозга дали следующий тестовой код воспроизводящий проблему.


КОму интересно, попробуйте угадать результат

ini_set ( 'display_errors', 'on' );
error_reporting ( E_ALL );

class MyDB extends PDO {
public $connectionId = 0;
}

class MyFactory {

protected static $_storage = array ();

static public function get($id) {
if ( isset ( self::$_storage[$id] ))
return self::$_storage[$id];

self::$_storage[$id] = new MyDB ( 'mysql:host=127.0.0.1;dbname=toox2;', 'root', '123', array (PDO::ATTR_PERSISTENT => true ) );
self::$_storage[$id]->connectionId = $id;
return self::$_storage[$id];
}
}

$db1 = MyFactory::get( 1 );
$db2 = MyFactory::get( 2 );

var_dump( $db1 );
var_dump( $db2 );




Ну а для тех кто думал ровно как и я что в результате $dbq->connectionId и $db2->connectionId буду разными привожу код


cd@cd-laptop:~/tmp/php-tests$ php test.php
object(MyDB)#1 (1) {
["connectionId"]=>
int(2)
}
object(MyDB)#2 (1) {
["connectionId"]=>
int(2)
}



Проблема в

array (PDO::ATTR_PERSISTENT => true )


Если его убрать, то поведение становится ожидаемым, но однако, почему persistent connect ломает ооп в пхп :)

А ну и на закуску, хотя из приведенного кода выше это видно

var_dump($db1 === $db2) === false

Кому интересно

cd@cd-laptop:~/tmp/php-tests$ php -v
PHP 5.2.4-2ubuntu5.3 with Suhosin-Patch 0.9.6.2 (cli) (built: Jul 23 2008 06:44:49)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies


На slackware товарищ воспроизводил проблему также.

Wednesday, October 22, 2008

memcache namespace php? выход похоже найден

Наткнулся на.

http://code.google.com/p/memcached-tag/

Проект содержит патч к серверу memcached и патч к php-memcached расширению. В патче добавлен следующий функционал.

Есть таги. (Пространство имен)
Ключ можно привязать к тагу
tag_add tag key

Все ключи относящиеся к одному тагу можно махом грохнуть
tag_del tag

Если у тага не остается ни одного ключа, он убивается автоматически.

То что нужно, пойду собирать и пропихивать чтобы нам его в тестовом режиме начали ставить.
/me очень рад :)


Update Сбт Окт 25 00:49:01
Радость оказалась преждевременной.

1. Скачать, пропатчить, собрать не вызвало проблем хоть и не делал этого ни разу. Единственное пришлость переобозвать папку.
2. Заявленный функционал работает.
3. Проверил жалобы, не нашел, но на сайте проекта висит баг от May 02, 2008 http://code.google.com/p/memcached-tag/issues/detail?id=1 и за полгода до сих пор не поправлен.
4. Баг был мной повторен.
5. Сей код был отдан на растерзание нашему cpp отделу, квалификация товарищей сомнений не вызвает.
Резюме. Посоветовавшись с cpp отделом, решили что чем использовать эту поделку, они нам предоставят свою реализацию.

Жду, думаю несколько месяцев до тестов.

В качестве вариантов находили несколько реализаций на java. По отзывам товарищей все эти реализации медленнее memcached, из за обилия разных финтифлюшек, где namespaces самая мелкая финтифлюшка.



Пример создания trac macros

Cтавим trac (на примере debian)

sudo apt-get install trac


Подготавливаем папку для тестов

cd@cd-laptop: cd
cd@cd-laptop: mkdir tests
cd@cd-laptop: cd tests
cd@cd-laptop: mkdir tracdir
cd@cd-laptop: trac-admin tracdir initenv
жмем энтер :)
Запускаем через stand-along
cd@cd-laptop: tracd /home/cd/tests/tracdir --port 8888
cd@cd-laptop: firefox http://127.0.0.1:8888/tracdir



Трак установлен, для тестирования нам такого хватит.

останавливаем

cd@cd-laptop: Ctrl+C
cd@cd-laptop:


Создаем macro Hello

cd plugins
touch Hello.py



Содержимое Hello.py

from trac.core import *
from trac.wiki.macros import WikiMacroBase
from trac.util import escape

__all__ = ['HelloMacro']

class HelloMacro(WikiMacroBase):
def expand_macro(self,formatter, name, args):
return "Hello %s" % args or '

Использование. Внутри тикета или wiki напишите


[[Hello(cd)]]

Результат

Hello cd

Т.е. для написания минимального макроса, достаточно отнаследоваться от WikiMacroBase, реализовать один метод, возращающий html.

Пример ставшего для меня полезным макроса.

1 from trac.core import *
2 from trac.wiki.macros import WikiMacroBase
3 from trac.util import escape
4 from trac.util.html import escape,html,plaintext
5 from genshi.builder import tag
6
7
8 __all__ = ['XxxMacro']
9
10
11 class RwebtableMacro(WikiMacroBase):
12 """
13 Show table schema
14 {{{
15 [[Xxx(tablename)]]
16 }}}
17
18 """
19 def expand_macro(self, formatter, name, args):
20 # args will be `None` if the macro is called without parenthesis.
21 table = args or ''
22 if not table :
23 return 'no table specified'
24
25 import MySQLdb
26 user = self.env.config.get('cddb', 'user')#требует опции в trac.ini секции [cddb]
27 host = self.env.config.get('cddb', 'host')
28 passwd = self.env.config.get('cddb', 'passwd')
29 dbname = self.env.config.get('cddb', 'dbname')
30 db = MySQLdb.connect(user=user, host=host, passwd=passwd, db=dbname)
31 cursor = db.cursor()
32 sql = "DESCRIBE %s" % table
33 cursor.execute(sql)
34 table = tag.table(border=1)
35 table.append(tag.tr( tag.th('Field'), tag.th('Type') , tag.th('Null') , tag.th('Key') , tag.th('Default'), tag.th('Extra') ) )
36 res = cursor.fetchall()
37 for row in res :
38 tr = tag.tr()
39 for i in row :
40 tr.append(tag.td(str(i)) )
41 table.append(tr)
42 return str(table)




Например
[[Xxxtable(country)]]

FieldType Null Key Default Extra
country_idint(11)NOPRINoneauto_increment
isovarchar(3)YESNone
namevarchar(50)YESNone

Tuesday, October 21, 2008

hook pre-commit в svn. Реализация на питоне.

Задача : при коммите в svn, хочется чего то делать, например закрывать тикет в trac.

Для этого в svn есть механизм хуков. Далее по шагам как я это делал я.

Создание репозитория, начальный checkout

директория для тестов
cd@cd-laptop:~$ mkdir tests
cd@cd-laptop:~$ cd tests
директория для репозитория
cd@cd-laptop:~/tests$ mkdir repo
создание репозитория
cd@cd-laptop:~/tests$ svnadmin create repo
директория для чекаутов
cd@cd-laptop:~/tests$ mkdir checkout-dir
первый чекаут
cd@cd-laptop:~/tests$ svn checkout file:///home/cd/tests/repo/ checkout-dir/
Checked out revision 0.
создаем, добавляем, коммитим файл.
cd@cd-laptop:~/tests$ touch checkout-dir/x.txt
cd@cd-laptop:~/tests$ svn add checkout-dir/x.txt
checkout-dir/x.txt
cd@cd-laptop:~/tests$ svn ci checkout-dir/x.txt -m'first commit'
Adding checkout-dir/x.txt
Transmitting file data .
Committed revision 1.
cd@cd-laptop:~/tests$




Репозиторий создан, для теста сделал один коммит.

Теперь собственно создаем хук.


Заходим в папку репозитоия
cd@cd-laptop:~/tests$ cd repo/
смотрим чего есть. Интересует папка hooks
cd@cd-laptop:~/tests/repo$ ls
conf dav db format hooks locks README.txt
cd@cd-laptop:~/tests/repo$ cd hooks/
Заходим в нее.
cd@cd-laptop:~/tests/repo/hooks$ ls
post-commit.tmpl post-revprop-change.tmpl pre-commit.tmpl pre-revprop-change.tmpl start-commit.tmpl
post-lock.tmpl post-unlock.tmpl pre-lock.tmpl pre-unlock.tmpl
Создаем, делаем права на запуск, заполняем
cd@cd-laptop:~/tests/repo/hooks$ touch pre-commit
cd@cd-laptop:~/tests/repo/hooks$ chmod +x pre-commit
cd@cd-laptop:~/tests/repo/hooks$ cat > pre-commit
#!/usr/bin/python

import sys

sys.stderr.write('Test error')
sys.exit(1)

cd@cd-laptop:~/tests/repo/hooks$ cd ../..
cd@cd-laptop:~/tests$ echo '1' >> checkout-dir/x.txt
cd@cd-laptop:~/tests$ svn ci checkout-dir/x.txt -m'test'
Sending checkout-dir/x.txt
Transmitting file data .svn: Commit failed (details follow):
svn: 'pre-commit' hook failed with error output:
Test error
cd@cd-laptop:~/tests$



В папке репозитория в подпапке hoosk был создан файл pre-commit и ему поставлены права на запуск.
Теперь когда свн его увидит, будет запускать после получения данных непосредственно перед коммитом. При коде возврата не 0, commit не пройдет. Собственно сам скрипт можно писать на любом скриптовом языке, главное чтобы была шибанг строка.

Подробнее о коде pre-commit

#!/usr/bin/python

import sys

#пишем в STDERR
sys.stderr.write('Test error')
#выходим с кодом возврата 1
sys.exit(1)



Т.е. сам хук создан, он запускается, не дает нам сделать коммит, теперь хочется уметь работать с информацией, которая приходит перед коммитом. Нас интересуют такие вещи как
автор,
текст комментария,
содержимое файлов и т.д.


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


Для получения информации о предстоящем коммите, полезна утилита

svnlook


Формат запуска следующий

svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]

Где subcommand одна из следующего списка

author
cat
changed
date
diff
dirs-changed
help (?, h)
history
info
lock
log
propget (pget, pg)
proplist (plist, pl)
tree
uuid
youngest



REPOS_PATH путь до репозитория, аргументы и опции могут зависеть от subcommand нас интересуют только две

any subcommand which takes the '--revision' and '--transaction'


--revision понятно, выполняет subcommand по определенной ревизии
--transaction интереснее, выполняет subcommand по транзакции, с этим я не разобрался, но понял так.

Если взглянуть на pre-commit.tpl (файл созданные в репозитории по умолчанию) увидим там следующее


REPOS="$1"
TXN="$2"

# Make sure that the log message contains some text.
SVNLOOK=/usr/bin/svnlook
$SVNLOOK log -t "$TXN" "$REPOS" | grep "[a-zA-Z0-9]" > /dev/null || exit 1


Где видно что параметр --transaction он же -t используется с переменной TXN, которая является вторым аргументом передаваемым в pre-commit. Т.е. эта штука у нас на руках, и svnlook умеет с ней работать.

Итак при обработке pre-commit у нас нет ревизии (мы ее создаем ) зато есть транзакция. Теперь что мы можем из этого получить.

Пример pre-commit который выводит автора и не дает закоммититься.


#!/usr/bin/python

import sys
import os

#обертка над svnlook для запуска любой команды
def cmd_out(subcommand, transaction, repo) :
cmd = '/usr/local/bin/svnlook %s -t "%s" "%s"' % (subcommand, transaction, repo)
str = os.popen(cmd, 'r').read()
return str.strip('\n')

#вызвает нашу оберку, для подкомманды author
def look_author(transaction, repo) :
return cmd_out('author', transaction, repo )

#читаем репозиторий, транзакцию
repos = sys.argv[1]
txn = sys.argv[2]

#получаем автора
author = look_author(txn, repos)

#пишем автора в STDERR
sys.stderr.write('Author :%s:' % author )
#выходим с ошибкой, запрещая коммит
sys.exit(1)


Единственное нужно указать правильный путь до svnlook

Ну теперь собственно все. Т.е. при помощи svnlook можно получить очень много информации о предстоящем коммите :)

Например получения текста лога для данного коммита.


def look_log (transaction, repo) :
return cmd_out('log', transaction, repo)


И так далее. Ну а наконец, скрипт который просматривает лог, ищет в определенном формате номер тикета, и пишет комментарий в трак к тикету, и закрывает тикет по необходимости.


#!/usr/bin/python

import sys, os, string, re

# return true or false if this passed string is a valid comment

php_devs = ('xx', 'xxx', 'yy', 'yyy', 'zz', 'zzz') #only for this dev's rules allowed



def cmd_out(subcommand, transaction, repo) :
cmd = '/usr/local/bin/svnlook %s -t "%s" "%s"' % (subcommand, transaction, repo)
str = os.popen(cmd, 'r').read()
return str.strip('\n')

def look_info(trans, repo) :
return cmd_out('tree --full-paths', trans, repo)

def look_author(transaction, repo) :
return cmd_out('author', transaction, repo )

def look_log (transaction, repo) :
return cmd_out('log', transaction, repo)

def check_comments(comment ):#return ticket id if find in comment
p = re.compile('\s*ticket:(\d+)(:close)?\s+(.*)$', re.MULTILINE)
res = p.match(comment)
try:
ticket_id = res.group(1)
except:
ticket_id = None
return ticket_id

def need_close(comment) : #i know it's copy paste, but i had choice, write into blog, or rewrite it ...
p = re.compile('\s*ticket:(\d+)(:(close))?\s+(.*)$', re.MULTILINE)
res = p.match(comment)
try:
is_closed = res.group(3)
except:
is_closed = ''
return is_closed == 'close'


def update_ticket_in_trac(ticket, author, comment, data ) :
import xmlrpclib
s = xmlrpclib.ServerProxy('http://tracuser:tracpast@domain.com:port/path_to_Trac/login/xmlrpc')#create service
try:
ticketInfo = s.ticket.get(ticket)#try get ticket
status = ticketInfo[3]['status']
if status == 'closed' :
sys.stderr.write("Ticket already closed, need reopen it first !!!")
return 1
except: #can catch here protocol err, it mean no rights
sys.stderr.write("No ticket found in trac for id %s" % ticket )
return 1
comment = comment.replace(':close','')
s.ticket.update(ticket,comment.replace("ticket:%s" % ticket, '%s killed himself with message : \n' % author , data) , data)
return 0


def main(repos, txn):
comment = look_log(txn, repos)
author = look_author(txn,repos)
if not (author in php_devs) :
return 0
ticket = check_comments(comment)
if ticket == None :
sys.stderr.write("\nticket:num must be at the comment begin where num is ticket id, if not exists create it please!")
return 1
data = dict()
if ( need_close(comment) ) :
data['status'] = 'closed'
return update_ticket_in_truck(ticket, author,comment, data )

if __name__ == '__main__':
if len(sys.argv) < 3 :
sys.stderr.write("Usage: %s REPOS TXN\n" % (sys.argv[0]))
else:
sys.exit(main(sys.argv[1], sys.argv[2]))


Friday, October 17, 2008

Php, не все так плохо как кажется на первый взгляд.

   С полгода назад, пришлось реализовать для удобства IArrayAccess. И только после реализации решил замерить насколько оно медленнее массива, и запомнилась мне следующая удивительная цифра. Что то на 10.000 итераций Объект реализовавший IArrayAccess был медленнее на десяток секунд. (На не сильно слабой машине).


   Решил сегодня это печальное событие задокументировать с цифрами.  Результат приятно удивил.

Исходники
array.php

error_reporting(E_ALL);
$array = array();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = array();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}
?>




spl-array.php

class SplArrayTest implements ArrayAccess
{
protected $_data;

public function __construct()
{
$this->_data = array();
}

public function offsetExists($offset)
{
return isset($this->_data[$offset]);
}

public function offsetGet($offset)
{
return $this->_data[$offset];
}

public function offsetSet($offset, $value)
{
$this->_data[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->_data[$offset]);
}

public function __destruct()
{
unset($this->_data);
}
}


$array = new SplArrayTest();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = new SplArrayTest();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}

?>



spl2-array.php

class SplArrayTest implements ArrayAccess
{
public function __construct()
{
}

public function offsetExists($offset)
{
return isset($this->$offset);
}

public function offsetGet($offset)
{
return $this->$offset;
}

public function offsetSet($offset, $value)
{
$this->$offset = $value;
}
public function offsetUnset($offset)
{
$this->$offset;
}

public function __destruct()
{
}
}


$array = new SplArrayTest();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = new SplArrayTest();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}
?>



PHP 5.2.4-2ubuntu5.3  (2 ядра, системя с запущенными иксам, не сильно загружена)


Запуск через time php array.php
Среднее время выполнения - 1.6 секунды
Среднее время выполнения при включенном xcache  - 1.3
На нашем прод сервере + apc = 0.5 секунды

Запуск через time php spl-array.php
Среднее время выполнения - 3.9 секунды
Среднее время выполнения при включенном xcache  - 3.72
На нашем прод сервере + apc = 1.2 секунды

Запуск через time php spl2-array.php
Время выполнения 4.7 секунды
Среднее время выполнения при включенном xcache  - 4.2


Выводы.

На 1.000.000 элементарных операций разница всего на 2-3 секунды. На моем ноуте.
Разница в полсекунды на прод сервере.

Для примера в zend framework по умолчанию  порядка 2.000-4.000 таких и не очень элементарных вызовов.
В простейшей реализации страницы на limb
10.000 и выше. (за счет их любимых тулкитов).

Т.е. получается что для обычной страницы вы потеряете при активном использовании SPL IArrayAccess сотые доли секунды. Чем можно пренебрегать очень смело.

В итоге бояться использовать SPL  я перестаю, хотя все равно на душе будет не спокойно.



Thursday, October 16, 2008

сервера

  Долгое время не было времени ни на что.

  Сначала провозился с переездом хостинга, настройкой веб серверов. Долго писать не охота. Поэтому вкратце.

 1. Линух без свапа - говно.
 2. FreeBsd на малых ресурсах - рулит.
 3. Апач на малых ресурсах неадекватен.
 4. lighthttpd - рулит :)


Зато в перерыве устав нажимать ctrl +F для поиска в программе футбола, сваял. http://tv.quicknotes.ru/

Monday, September 29, 2008

django fixtures использование

Изучаю джанго.

Понадобились fixtures. Полез в новую джанго-книжку, написано много, как использовать. А как их создавать непонятно.
В итоге.

Пусть есть модель page для ее есть админка, лежит в application yyy

Я делал так.


    1. Создал необходимые данные для этой модели через админку.
    2. Перешел в директорию проекта набрал ./manage.py dumpdata --indent=2  yyy > ./data/fixtures/xxx.json
    3. Прописал в settings.py FIXTURE_DIRS = (  PROJECT_ROOT + '/data/fixtures/',)
    4. И уже при установке сначала ./manage syncdb
    5. Затем ./manage.py loaddata xxx

Tuesday, September 23, 2008

Конвертирование ошибок в exceptions.

Задача
Преобразовать все ошибки в исключения (exceptions) для более удобной их обработки.
Решение подсмотрено на php.net в комментариях.

class My_Exception extends Exception {
public static function errorHandlerCallback($code, $string, $file, $line, $context)
{
$e = new self($string, $code);
$e->line = $line;
$e->file = $file;
$e->context = $context;
throw $e;
}

}

set_error_handler(array("My_Exception", "errorHandlerCallback"), E_ALL);



Теперь практически все ошибки будут преобразованны в Exception. Теперь достаточно легко и непринужденно мы можем отлавливать ошибки php в одном месте, например



// bla bla bla
try
{
$controller->dosmth();
}
catch( Controller_Wrong_Data_Exeption $e)
{
// do smth
}
catch ( My_Exception $e)
{
// we know it's php error
}



Такой метод не ловит фаталы, а очень хочется. Где то встречал решение с использованием особенности ob_start, ob_flush, если найду завтра добавлю.

Monday, September 22, 2008

Php, агрегация, псевдонаследование

Задача на примере.
 Есть класс View. Хочется создать класс Cache_View который будет агрегировать внутри себя объект класса $view, а внешне иметь такой же интерфейс как и класс view.

Понятно что на это многие покрутят пальцем у виска, отправят учить ооп, и скажут что нибудь про наследование. Для остальных продолжу мысль.

Простейшая реализация (наследование не подходит, нужно агрегировать)- агрегировать, и определить кучу методов вида


public function xxx($param1,$param2)
{
return $this->agregatedObject->xxx($param1, $param2);
}
?>



Реализация где не придется реализовывать эту кучу методов.


class A
{
public function f1()
{
}
}

class B
{
protected $_a = null;
public function __construct(A $a)
{
$this->_a = $a;
}
public function __call($function, $params)
{
return call_user_func_array(array(&$this->_a, "A::$function"), $params );
}
}

//как работает
$a = A();
$b = B($a);

$a->f1();
$b->f1();// тоже самое что и строчкой выше. Произойдет вызов метода f1 объекта $a
?>


Если кому то еще интересно, ниже пример использования.

class View
{
public function render($template)
{
//return html
}
public function f1()
{
}
//...
public function f120()
{
}
}

class Cache_View
{
protected $_view = null;
protected $_cache = null
public function setCache($cache)
{
$this->_cache = $cache;
}
public function __construct(View $v)
{
$this->_view = $v;
}
public function __call($function, $params)
{
return call_user_func_array(array(&$this->_view, "View::$function"), $params );
}

public function render($template)
{
if ( $this->_cache != null && ! $this->_cache->mis($this->makeKey($template))// если объект кэша есть и в нем чего то лежит
return $this->_cache->get($this->makeKey($template));// то вернем закэшированные данные

$out = $this->_view->render($template);
if ( $this->_cache != null)
$this->_cache->set($this->makeKey($template), $out);
return $out;
}

public function makeKey($str)
{
retirm md5($str);
}
}

//примеры исползования

$view = View();
$cache = Cache::create(Cache::File);
$cachedView = Cached_View($view);
$cachedView->setCache($cache):

$view->test();
$cachedView->test();// тоже самое что и строчкой выше. Вызов метода агрегированного класса
$cachedView->render('splash.php'); // а вот здесь будет предприняты попытка заглянуть в кэш и вернуть закэшированные результаты, если их нет, то произойдет отработка обычного $view->render и результат положится в темплейт
?>


Пример реализации чего то похожего внешне на множественное наследование



class Base()
{
public function base_function() {};
}

class A
{
public function f1()
{
}
}

class B extends Base
{
protected $_a = null;
public function __construct(A $a)
{
$this->_a = $a;
}
public function __call($function, $params)
{
return call_user_func_array(array(&$this->_a, "A::$function"), $params );// понятно что тут можно проверять наличичие метода поочереди у пула объектов, и вызывать их
}
}

$a = new A();
$b = new B($a);
$b->base_function(); //ok child Base
$b->f1(); // looks like child A

Wednesday, September 17, 2008

Простые вещи, php

При работе с проектом хотелось не слишком много, просто отслеживать время выполнения скриптов, а также количество вызвов скриптов.
   Простое решение - в конце каждого скрипта ( у нас правда это один index.php) пишется в файл uri + и время выполнения скрипта.

  Однако выяснилось, что из за кусков кода вроде этого

header("Location: bla bla bla");
exit();

Некоторые вещи просто не ловятся. А очень хочется понять не в них ли вся проблема :)

Решение оказалось простым. Использовать

register_shutdown_function('callback');

Например в нашем проекте есть класс Rapid в котором лежат жизненно необходимые нам вещи.

class Rapid
{
//...
  public static function quit()
  {
    //write to log
  }
//..
}
// тут как раз регистрируем статическую функцию, как функцию по завершению
register_shutdown_function(array('Rapid', 'quit'));

Ньюанс : эта функция вызвается даже после php fatal error!

cd@cd-laptop:~/tmp$ cat 2.php


register_shutdown_function('quit');
echo 'start';
require_once('none.php'); // у меня нет файла, будет фатал.
echo 'end';
function quit()
{
    echo 'quit';
}


cd@cd-laptop:~/tmp$ php 2.php
start
Warning: require_once(none.php): failed to open stream: No such file or directory in /home/cd/tmp/2.php on line 7
Fatal error: require_once(): Failed opening required 'none.php' (include_path='.:/usr/share/php:/usr/share/pear') in /home/cd/tmp/2.php on line 7
quit
cd@cd-laptop:~/tmp$

Sunday, July 20, 2008

Распределённые системы управления версиями

Давным давно читал похвальные отзывы об mercurial, bazaar и иже с ними.
В силу природного любопытства нашел таки время прочитать про них подробнее - понравилось.
Очень порадовала возможность забирать себе изменения с какого то конкретного бранча, причем делается это легко и непринужденно.
Прочитав - забыл. Т.к. основной проект на svn и проблема в том чтобы обучить дизайнеров и flash программистов новой софтине (на svn то с трудом заставили их переходить).
Тут возникает новый проект, ни с чем не связанный, отказываемся от php (устал я от него) в пользу python + django (интересно, удобно) заодно решили и репозиторий сменить.
Из консоли вдвоем поделали тесты (я bazaar, другой сотрудник mercurial) в общем простейшие операции прошли на ура. Все просто. (Для базаар пришлось пару модулей питоновских поставить только)

В чем же жопа? В плагинах к эклипсу. Я давным давно пришел ко мнению что ide должно быть ide а не редактором, и eclipse (полюбился он мне тем, что есть поддержка многих языков) плагины к двум вышеперечисленным клиентам убогие. mercurial плагин отмел сразу, три кнопки и в тех не разобраться. С bazaar ковырялся чуть подольше в надежде на чудо, сам плагин нормальный, в плане локальный commit, diff etc... . Танцы с бубном начались на push, и заявленная поддержка (только sftp) у меня не работала.
Осталось впечатление крайней сырости и недоделанности.

В общем как ни грустно, буду сидеть на svn + через полгодика попробую опять.

P.S. В голове еще крутится идея чтобы испльзовать bzr-svn, но крайне боюсь глюков, что может быть черевато на основном проекте, хотя может через некоторое время попробую, хотя бы из интереса.

Tuesday, July 15, 2008

debian/ubuntu apt-get NO_PUBKEY

Все время забываю
Проблема

apt-get update
W: GPG error: http://mirror.yandex.ru stable Release: Следующие подписи не могут быть проверены, так как недоступен общий ключ: NO_PUBKEY 07DC563D1F41B907


Решение

# gpg --keyserver wwwkeys.eu.pgp.net --recv-keys 07DC563D1F41B907
gpg: requesting key 1F41B907 from hkp server wwwkeys.eu.pgp.net
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key 1F41B907: public key "Christian Marillat <marillat@debian.org>" imported
gpg: no ultimately trusted keys found
gpg: Total number processed: 1
gpg: imported: 1
# gpg --armor --export 1F41B907 | apt-key add -


Wednesday, July 9, 2008

Все гениальное просто

Блин. Сколько я мучался с zend debuger + zend studio.
А ларчик открывался просто

Thursday, May 8, 2008
ZSE + XDEBUG
3:07AM PDT · dtaylor7
I think Zend hates xdebug... But the ZSE contains a full PDT (it has xdebug support). So, remove (move) the plugins/com.zend.php.debug*, then start the studion with "ZendStudio -clean"

Hmm, there will be xdebug support in the PHP Debugger select... I tested it, works.


test

test

Sunday, June 22, 2008

Флаги языков в апплете индикатора расскладки gnome.

Долго мучался без флагов с дефолтным индикатором расскладки.
В итоге нашел решение.
копируем флаги в папоку ~/.icons/flags
обызываем ru.png us.png
открываем gconf-editor
desktop/gnome/peripherals/keyboard/indicator/showFlags

ставиь галочку show country flag
и все.

Tuesday, April 29, 2008

perl

Проработав с php забываю какой красивый перл
Скрипт который парсит логи и выводит список проблемных пользователей



perl -n -e '$out{$1}=1 if /user (\S+) without confirm/ ;END{ print keys %out}'





Powered by ScribeFire.

Сроки разработки. Боян но правда.

Если бы не сталкивался с таким каждый день, может быть так и не ржал.

Краткая памятка о сроках по работе с [неразборчиво].«Сегодня» — завтра.
«Завтра» — напомнить завтра, что уже сегодня (см. «сегодня»).
«В течение недели» — в следующую среду.
«В течение недели, но до выходных, пожалуйста» — в понедельник.
«Через две недели» — месяц.*
«Месяц» — неопределенная, очень большая величина времени.
«Три месяца» — три неопределенные, очень большие величины времени.
«К осени» — когда выпадет снег. Снег выпадает каждый год, поэтому «к осени» является наиболее благоприятным сроком, пропустить который практически невозможно.
«Через год» — не используется, ибо есть «к осени».
* — Популярно заблуждение, что две недели — это 14 дней. Это не так. Две недели — это 14 дней + «в течение недели» (ибо вторая неделя еще не кончилась) + завтра («один день погоды не сделает»). В особых случаях отсчет «двух недель» начинается со следующего понедельника, так выигрывается еще несколько дней.Если повезет, то в результате выходит месяц срока и опоздание всего на один день («завтра»).

Powered by ScribeFire.

Tuesday, February 12, 2008

It работа

Включил на работе все протоколы что были в пиджине ( раньше висел тока рабочий jabber)
По интенсивности валящихся мессаг возникает ощущение что на работе никто не работает.

Powered by ScribeFire.

Sunday, February 10, 2008

Eclipse pdt ubuntu 7.10 x64

Срочно понадобился PDT,
скачал с сайта all_in_one
(все время глючит когда ставлю плагинами)
Не работает. Пишет javat terminated error code 13 и ругается на loaders/svg_loader.so

Помимо решения, скачать 64 битный еклипс и установить pdt через менеджер обновлений,
нашел вот что

http://dmartin.org/weblog/eclipse-on-ubuntu-linux-for-amd64

В кратце

Ставим жаву

#sudo apt-get install ia32-sun-java5-bin
в моем случае
sudo apt-get install ia32-sun-java6-bin



Делаем ярлык для запуска куда провисываем путь до джавы.

cd ~/eclipse // перехожу в папку где установлен эклипс
touch eclipse.sh
chmod +x eclipse.sh
vim eclipse.sh


#!/bin/bash
PATH=/usr/lib/jvm/ia32-java-6-sun/bin:$PATH
/home/user/eclipse/eclipse
chmod +x eclipse.sh




Powered by ScribeFire.

Thursday, February 7, 2008

squid http proxy минимум

Через провайдера на работе не было связи до моего домашнего провайдера, а там для меня лежала нужная инфа.
Через хостинг линксом все работает, но не удобно.


apt-get install squid

vim /etc/squid/squid.conf
нашел
http_access allow localhost

Поменял на
acl allowed_hosts src xx.xx.xx.xx/255.255.255.0
http_access allow allowed_hosts

Где xx.xx.xx.xx мой айпи наружу.
/etc/init.d/squid restart

Firefox + mm proxy swith

Прописываю proxy myhosting:3128

Все работает






Powered by ScribeFire.

Tuesday, January 29, 2008

Отрыжка камеди.

Посмотрел это гавно "Самый лучший фильм" (с) Экслер.
Лучше не скажешь.
Я просто в шоке, как можно нае**ть людей раскрутить фильм.
В общем пендосовская херня комедия, да какая нафик комедия. Улыбнулся три раза, остальное время был в шоке.
Смотрел для галочки, просто в ужасе. Есть подозрение что если бы не харламов, плевался бы раза в 2 меньше.



Powered by ScribeFire.

Sunday, January 27, 2008

Этиология и патогенез московских пробок.

Интересная статья
http://polit.ru/lectures/2008/01/24/probki_print.html
Вещи очень интересные и познавательные. Можно многое извлечь для себя.
Однако по окончании прочтения стало грустно, особенно после фразы.

Принимают решение одни, а знают другие.

Имхо ключевая фраза, особенно по всем вопроса/проектам м которые вызывают удивление.




Powered by ScribeFire.

Friday, January 25, 2008

ssh авторизация через ключи? или логин без пароля на сервер


Updated at 28 october 2008 04:32
Проблема : надоело вводить пароль на ssh хост(пипец длинный)

Решение.
1. Нужно сгенерить ключ (если еще не сделан)

[user@host ~]$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Место для хранения ключа (я нажал enter)
Enter file in which to save the key (/home/user/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx user@host
[user@host ~]$


Ключ сгенерен, теперь нужно положить его на серве

user@host$ ssh-copy-id user2@host2
Now try logging into the machine, with "ssh 'user2@host2'", and check in:

.ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.




Все, теперь можете логиниться.

Если нет ssh-copy-id

Можно сделать так


Копируем через scp
user@host$ scp ~/.ssh/id_rsa.pub user2@host2:/home/user2/
Коннектимся
ДОбавляем публичный ключ
user@host2$cat ~/id_rsa.pub >> ~/.ssh/authorized_keys2



Текст первоначальной статьи

Проблема : надоело вводить пароль на хост(пипец длинный)
Есть домашний комп, и хост.
Например
user@desktop, user2@server


user@desktop:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/cd/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
// оставил пустыми
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx user@desktop

user@desktop:~$ cat /home/user/.ssh/id_rsa.pub
ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx== user@desktop


Копируем от ssh-rsa до user включительно (красным)

Коннектимся на сервер

vim .ssh/authorized_keys2

Получаем

ssh-rsa ннннннннннннн== another-user
пустая строка
ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx== user



Режим редактирования, идем в конец файла, я вставил скопированную срочку через пробел

:wq (сохраняем закрываем)

Теперь пускает без всякого пароля

Updated at 25 october 2008 04:32

Блин только щас узнал что все эти танцы с бубном заменяются на одну простую вещь :) Понятно правда что генерацию ключа никто не отменял

$ssh-copy-id login@host


Thursday, January 24, 2008

Настройка Subversion в Debian Etch

понадобился svn репозитарий для своего хостинга (firstvds.ru) система debian etch т.к. хостинг свехдешевый, и места во время создания было мало.

Человек я ленивый и далеко не админ. Читать svn hand book лень.
Благо добрые люди выложили описание step by step
Копипаст и все работает.
Единственное
askeet замените на название своего репозитария. 

localhost (в конце при разметке репозитария на firstvds.ru нужнозаменить на 127.0.0.1)

репозитарий будет доступен потом по урл http://host/svn/name_of_repository

http://ubunterra.blogspot.com/2007/11/subversion-debian-etch.html

Если нужно чтобы пароль был на все операции закоменнтируете


#<LimitExcept GET PROPFIND OPTIONS REPORT>
Require valid-user
#</LimitExcept>

Thursday, January 17, 2008

Ubuntu gutsy 64b flash player

Установил себе 64 битную ubuntu gutsy.
Все хорошо, все отлично.
2 проблемы.
1. Skype - находил некоторые решения, но не работающие.
2. Flash player, поставил себе gnash с баннерами справляется. но не больше. Зашел на сайт starcraft2.com не смог посмотреть ролики :(. Расстроился.
В итоге нашел решение
http://www.daryl.mu/2008/01/12/howto-install-flash-9-on-ubuntu-gutsy-gibbon-64-bit/

Привожу для себя вырезку из этого блога.

wget http://fpdownload.macromedia.com/get/flashplayer
/current/install_flash_player_9_linux.tar.gz


tar -xvzf ./install_flash_player_9_linux.tar.gz


mkdir ~/.mozilla/plugins


cp install_flash_player_9_linux/libflashplayer.so ~/.mozilla/firefox/plugins


sudo apt-get install nspluginwrapper


nspluginwrapper -i ~/.mozilla/plugins/libflashplayer.so

Тут мне выдало

nspluginwrapper -i ~/.mozilla/plugins/libflashplayer.sonspluginwrapper: /home/cd/.mozilla/plugins/libflashplayer.so is not a valid NPAPI plugin

Оказалось что файл ~/.mozilla/plugins/libflashplayer.so просто не скопировался.
Updated

Иногда падает, флеш не отображается, иногда отэтого помогает рефреш страницы.

Sunday, January 6, 2008

Моя новая убунту

На праздниках для отдохновения души поставил себе ubuntu 7.10 с нуля. До этого была перекуроченная тестовая версия убунты почти годичной давности которая тихо умирала.
Девиз был ни грамма kde :)

Получилось интерсно





Сама система как и водится встала без проблем.
Для включения compiz потребовалась установка проприетарных драйверов (ati radeon x1100)
После них заработал compiz. В отличии от моей предыдущей установки найстройки применяются не сразу, а после рестарта компиза.

Внешний вид настроился очень просто
Внешний вид -&gt; темы (скачал с gnome-look.org blubuntu-murrine) установил
Gdm -&gt; leopard theme (к сожалению скрина под рукой нет, но выглядит отпадно)
Icons -&gt; Vista-inspirate1_0 (kde-look.org) for gnome (по старой привычке)
Прозрачная панель.
Emerald theme aqua-button-look (подходит под wallpaper)
Вместо window manager -&gt; avant-window-manager к нему же поставил плагинами меню, переключатель окон, и быструю панель ( в vim ее использовать не получилось по esc закрывается)

Вначале пробовал gdesklets они начали безбожно глючить, поставил всместо них screenlets for compiz
Календари screenlets берут информацию о событиях из google.calendar.com
Получилось неплохо на мой взгляд.

Gnome-terminal прозрачный внутри, мне удобно.
KDocker для gnome-terminal (одну консоль держу постоянно открытой и сворачиваю ее в трей, при использовании alltray у меня не работает прозрачность)
Alltray для evolution

File manager - mucommander



Powered by ScribeFire.

 
Каталог сайтов, Добавить сайт