среда, 3 июня 2015 г.

PHP extensions - zend_parse_parameters

Запрос аргументов

Новый API разбора параметров: В этой главе задокументирован новый Zend API разбора параметров, автор Andrei Zmievski. Он был введён в период между PHP 4.0.6 и 4.1.0.
Разбор параметров это самая распространённая и утомительная операция. Было бы удобно также иметь стандартизованную проверку ошибок и механизм сообщений. Начиная с PHP 4.1.0, имеется способ делать всё это с помощью нового API разбора параметров. Он значительно упрощает процесс получения параметров, но недостаток его в том, что он не может использоваться в функциях, ожидающих переменное количество параметров. но поскольку большинство функций не входят в эту категорию, этот API разбора рекомендуется как новый стандартный способ.
Прототип функции разбора параметров таков:
int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...);
Первый аргумент этой функции это количество реально переданных функции параметров, так что ZEND_NUM_ARGS() может использоваться здесь. Второй параметр всегда должен быть макросом TSRMLS_CC. Третий аргумент это строка, специфицирующая количество и типы аргументов, ожидаемых вашей функцией, аналогично тому, как строка форматирования printf специфицирует количество и формат выводимых значений, с которыми она должна работать. И, наконец, остальные аргументы это указатели на переменные, которые должны принимать значения от параметров.
zend_parse_parameters() выполняет также, где возможно, конвертацию типов, так что вы всегда получаете данные в том формате, который запросили. Любой скалярный тип может быть конвертирован в другой, но конвертация между сложными типами (массивами, объектами и ресурсами) и скалярными типами не допускается.
Если параметр может быть успешно получен и в процессе конвертации не было ошибок, функция возвратит SUCCESS, иначе - FAILURE. Функция выводит информативные сообщения об ошибках, если количество полученных параметров не совпадает с запрашиваемым количеством или если конвертация типов не может  быть выполнена.
Вот некоторые примеры сообщений об ошибках:
Warning! - ini_get_all() requires at most 1 parameter, 2 given

Warning! - wddx_deserialize() expects parameter 1 to be string, array given
Естественно, каждое сообщение об ошибке сопровождается именем файла и строкой, в которой ошибка возникла.
Вот полный список спецификаторов типов:
  • l - long
  • d - double
  • s - string/строка (с возможным нулевым количеством байтов) и её длина
  • b - boolean
  • r - ресурс, хранимый в zval*
  • a - array/массив, хранимый в zval*
  • o - object/объект (любого класса), хранимый в zval*
  • O - object/объект (класса, специфицированного вхождением класса), хранимый в zval*
  • z - текущий zval*
Следующие символы также имеют значение в строке спецификатора:
  • | - указывает, что оставшиеся параметры являются необязательными. Переменные для хранения, соответствующие этим параметрам, должны быть инициализированы значениями по умолчанию расширением, поскольку они не будут затронуты разбирающей функцией, если параметры не переданы.
  • / - разбирающая функция вызывает SEPARATE_ZVAL_IF_NOT_REF() для следующего за ней параметра, чтобы предоставить копию этого параметра, если только это не ссылка.
  • ! - следующий за ним параметр может быть специфицированного типа или NULL (применяется только к a, o, O, r или z). Если значение NULL передаётся пользователем, хранимый указатель будет установлен в NULL.
Лучше всего показать работу этой функции на примерах:
/* Получить long, string и её длину и zval. */
long l;
char *s;
int s_len;
zval *param;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
                          "lsz", &l, &s, &s_len, &param) == FAILURE) {
    return;
}

* Получить объект класса, специфицированного my_ce, и необязательное double. */
zval *obj;
double d = 0.5;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
                          "O|d", &obj, my_ce, &d) == FAILURE) {
    return;
}

/* Получить объект или null и массив.
   Если null передаётся для объекта, obj будет установлен в NULL. */
zval *obj;
zval *arr;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O!a", &obj, &arr) == FAILURE) {
    return;
}

/* Получить массив. */
zval *arr;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/", &arr) == FAILURE) {
    return;
}

/* Получить только первые три параметра (используется для varargs-функций). */
zval *z;
zend_bool b;
zval *r;
if (zend_parse_parameters(3, "zbr!", &z, &b, &r) == FAILURE) {
    return;
}
Обратите внимание, что в третьем примере мы передаём 3 для числа принимаемых получаемых параметров вместо ZEND_NUM_ARGS(). Это позволяет получать наименьшее количество параметров, если наша функция ожидает их переменное количество. Разумеется, если вы хотите работать с остальными параметрами, вы должны использовать zend_get_parameters_array_ex() для их получения.
Функция-разборщик имеет расширенную версию, которая имеет дополнительный аргумент flags, управляющий её работой.
int zend_parse_parameters_ex(int flags, int num_args TSRMLS_DC, char *type_spec, ...);
Единственный флаг, который в настоящее время можно передавать, это ZEND_PARSE_PARAMS_QUIET, который указывает функции не выводить никаких сообщений об ошибках в ходе операции. Это можно использовать в функциях, которые ожидают несколько наборов совершенно разных аргументов, но вам придётся самостоятельно выводить сообщение об ошибке.
Например, вот как можно получить набор из трёх long или строку:
long l1, l2, l3;
char *s;
if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
                             ZEND_NUM_ARGS() TSRMLS_CC,
                             "lll", &l1, &l2, &l3) == SUCCESS) {
    /* работа с long */
} else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
                                    ZEND_NUM_ARGS(), "s", &s, &s_len) == SUCCESS) {
    /* работа с string */
} else {
    php_error(E_WARNING, "%s() takes either three long values or a string as argument",
              get_active_function_name(TSRMLS_C));
    return;
}
С помощью рассмотренных способов получения параметров функцией вы должны были получить хорошее представление об этом процессе. Дополнительные примеры см. в исходном коде расширений, которые поставляются с PHP - они иллюстрируют каждый представленный случай.

Профилирование C

valgrind --tool=callgrind
./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
http://stackoverflow.com/a/771005

вторник, 19 марта 2013 г.

Unit test php

Часто разделяют автоматизированные тесты веб-приложений на 4 вида (это не касаясь нагрузочных, юзабилити и т. п.):

— приёмочные — проверяют конечный результат, как правило в браузере, то есть что непосредственно знает браузер (и как следствие показывает пользователю), получая html/js/css с сервера со всеми заголовками и т. п., проверяется не только приложение (php-код), но и сервер, и браузер (например JS-код) и прочая среда. Выполняются долго. Запускаются редко.

— функциональные — проверяют почти то же самое, но на более низком уровне, сервер и браузер уже не используются обычно, а проверяется вывод скрипта, то есть что html код соответствует ожидаемому, что в базу внесены нужные изменения и т. п. Зачастую используют эмуляторы браузеров. Выполняются быстрее. Запускаются чаще.

— интеграционные — опускаются ещё ниже, проверяется взаимодействие отдельных слоев приложения, например, что роутер, получив URL (или URI? вечно их путаю), вызовет контроллер с нужными параметрами и что контроллер, получив параметры передаст в шаблон нужные переменные (тесты роутера и тесты контроллера — обычно два разных теста). Выполняются относительно быстро, запускаются часто.

— модульные (юнит) — самый низкий уровень, тестирование отдельных методов, тупо что метод с параметрами возвращает нужное значение. Выполняются очень быстро, запускаются также часто, как нажимается кнопка save или run. В идеале автоматически при её нажатии :)

На самом деле разделение довольно условно и зачастую сложно однозначно отнести конкретный тест к одному из слоев (из-за лени разработчиков :) ) — он может быть, например, как и модульный с одной стороны (проверяем вывод в ответ на ввод), так и интеграционный с другой (вывод заключается в вызове другого метода с зависящими от входных параметров). На PHPunit в принципе их писать можно, но ни разу не видел такого.

По хорошему надо тестировать всё, но, скажем, приёмочные тесты отнимают довольно много времени и если нет отдельных тестировщиков, то ими часто пренебрегают (особенно если сложного JS на клиенте нет). Функциональные дают чуть меньшую уверенность в том, что приложение работает как ожидалось, но отнимают относительно мало времени, по сути один тест является спецификацией одной ветки выполнения, то есть переписыванием ТЗ с человеческого языка на алгоритмический в терминах приложения и ЯП. Интеграционные при желании оттестировать всё что можно занимают много времени и часто являются детализацией функциональных, разбивая их на несколько частей, что несколько снижает энтузиазм их написания («это же уже работает!»). Модульные пишутся просто (если архитектура нормальная) и быстро. Если считать функциональные спецификацией приложения, то модульные это спецификация методов, с хорошей архитектурой метод многого делать не должен и спецификации примитивны.

Внутреннему заказчику я бы посоветовал (особенно при работе с существующим приложением без тестов) для начала заказать, чтобы исполнители начинали с функциональных тестов (на новую функциональность или на ту, которую нужно изменить), как минимум для основной ветки выполнения (обычный сценарии работы) и использовали модульные в нетривиальных случаях, когда функциональными добраться до неосновной ветки выполнения сложно и/или долго (например для ситуаций типа «нет места на диске» или «сервер БД недоступен»). При обнаружении бага прежде чем искать его (особенно если сходу не понятно где он конкретно), сначала написать тест, который с этим багом заваливается, а без него должен пройти. При небольшом рефакторинге (типа выделения метода-объекта) писать модульные и интеграционные (если не сложно, а сложно быть не должно) тесты. 


Для получения доступа к приватным переменным достаточно использовать assertAttributeEquals
PHP:

<?php class FooTest extends PHPUnit_Framework_TestCase {
    public function 
testPrivateAttribute()
    {
        
$this->assertAttributeEquals(
          
'baz',  /* expected value */
          
'bar',  /* attribute name */
          
new Foo /* object         */
        
);
    }
?> 
А для доступа к приватным методам в PHP 5.3.2 появилась возможность вызывать такие методы через Reflection

PHP:

<?php class FooTest extends PHPUnit_Framework_TestCase {
    
/**
     * @covers Foo::doSomethingPrivate
     */
    
public function testPrivateMethod()
    {
        
$method = new ReflectionMethod(
          
'Foo''doSomethingPrivate'
        
);
 
        
$method->setAccessible(TRUE);
 
        
$this->assertEquals(
          
'blah'$method->invoke(new Foo)
        );
    }
?> 

суббота, 12 января 2013 г.

Git алиасы

 построения дерева визуального дерева всех веток

git log --graph --all --format=format:'%C(bold blue)%h%C(reset) — %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(bold white)— %an%C(reset)%C(bold yellow)%d%C(reset)' --abbrev-commit --date=relative

вторник, 8 января 2013 г.

Переменные окружения в php

Apache, htaccess:

SetEnv APP_ENV dev

Nginx:


fastcgi_param APP_ENV dev
#
# set APPLICATION_ENV variable for web application
#    production   - режим конечного использования
#    development  - режим разработки
#    testing      - настройки для тестирования на локальном сервере разработчика
#    staging      - настройки для тестирования на пре-продакшен сервере
#    maintenance  - обслуживание сервера
#
set $application_env production;
#if ( $host ~ ^beta\..+ ) {
if ( $remote_addr = 'bar' ) {
    set $application_env staging;
}

# Затем где задаются параметры fcgi прописать:

#
# set APPLICATION_ENV variable for web application
#
fastcgi_param   APPLICATION_ENV     $application_env;

пятница, 4 января 2013 г.

Prfomance 2


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

Обе программы есть в стандартном репозитории, следовательно установка выглядит так:

Производительность mysql

Первое, что рекомендуют при оптимизации любой БД в Unix системах, это включение опции noatime для раздела с данными БД.

При этом отключается запись информации о последнем обращении к файлам.
Эту опцию нужно прописать в файл /etc/fstab в качестве параметра монтирования ФС.

Для применения данного параметра без перезагрузки системы достаточно выполнить команду:

mount -o remount <точка монтирования раздела>

mount -o remount,noatime /


Далее выполним оптимизацию таблиц mysql, это делается следующей командой:

mysqloptimize --analyze --all-databases -u root -p

Примечание: только для таблиц типа myisam. Для innodb не пройдет.