Перейти к основному содержимому

14 записей с тегом "php"

Посмотреть все теги

Блокировка сессий в php

· 1 мин. чтения

Многие в курсе что при параллельных запросах от браузера к серверу, браузер старается ограничить число активных запросов (по ~5 на домен). Но та же проблема возникает и на бэкэнде с ресурсами

Одна проблема с БД решается транзакциями. Но вторая, более явная остаётся - сессии. В моём случае эта проблема вылезла при загрузке файлов в SPA-приложении. Делаете 20 POST запросов с файлами и параллельно ходите по сайту. Выглядит всё так, что покуда файл не загрузится и сервер не ответит, следующий GET не сработает и UI не обновится

Происходит это потому что php блокирует потоки где есть session_start(). В моём случае это все запросы, потому что есть авторизация

Решается очень просто:

session_write_close();

Закрываем явно запись в сессию. Читать мы всё ещё сможем оттуда, но зато следующие запросы смогут быстро исполняться. Я это делаю ещё до того как файл начнётся обрабатываться

Работа с бинарными данными в php

· 6 мин. чтения

PHP как язык плохо подходит для работы бинарными данными напрямую. Но иногда приложения должны взаимодействовать по таким протоколам, где размер пакетов очень важен или родным форматом данных для какого-то приложения, который никем в красивый json или xml не переводится.

Целые типы данных

Напоминаю какие типы данных есть в Си, на котором основан php

ТипРазмер памятиЗначений всего
char1 байт = 8 бит256
int2 байт = 16 бит2^16=65536
short2 байта
long4 байта = 32 бита4294967295

Char при этом используется универсально согласно ASCII табличке как в качестве явного кодирования текста, так и вспомогательными маркерами. Про float я умолчу, ибо мне не понадобилось.


Нотация

Если с бинарными данными не работать, то можно и забыть основы языка. С целыми числами на основании 10 всё понятно, но обычно значения длинных данных в них не пишут. Это объясняет табличку выше.

  • Бинарная, например 0b1011

  • Восьмеричная, например 0123

  • Шестнадцатиричная, например 0xF560B1A9

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

  • Unsigned int 14u
  • Long double или long int. Например 1.2l или 1L - фиг поймёшь, это единица или long
  • Float (с плавающей запятой) 1.3f или 1.3F

Итого, у нас char a будет хранить значение от -128 до 127.. ну или 256 если это extended ASCII. Отлично. Теперь как это использовать в php? 

Порядок битов

Честно, для меня было откровением что порядок в данных имеет значение. Я привык что 123 уже подразумевает где сотни, десятки и единицы, но для компьютера ведь всё равно. Конечно одно дело порядок написания для человека.. но тут другое - порядок записи байтов в зависимости от адреса. И больше всего удивительно что архитектуры на уровне работы с памятью разделились - x86 на стороне little endian, а за big endian SPARC и прочие. Поэтому если вы интегрируете разные архитектуры с бинарным форматом данных - договоритесь которая система будет работать.

Операции

  1. Бинарное чтение файла - fopen("binaryfile","b");

2. Битовые маски - популярный метод хранить много булевых значений в одной переменной и включать/проверять с помощью AND/OR операций. Если вы когда либо выполняли chmod 755, то уже включали эти флажки на привилий.

3. Побитовые сдвиги - помогают работать с битовыми масками и с битами в вообще

$b = $a **>>** 2; - сдвиг битов на две позиции вправо, тут может пригодится константа PHP_INT_SIZE $b = 8 >> 3; //1

4. Cyclic redundancy check

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

Вот пример самого короткого кода для CRC16 CCITT

function crc16_ССITT($data) {
$crc = 0xFFFF;
for($i = 0; $i < strlen($data); $i++) {
$x = (($crc >> 8) ^ ord($data[$i])) & 0xFF;
$x ^= $x >> 4;
$crc = (($crc << 8) ^ ($x << 12) ^ ($x << 5) ^ $x) & 0xFFFF;
}
return $crc;
}

5. Упаковка

Если вы обычную переменную в php начнёте сохранять в файл, то она наверняка не будет оптимальной. А если таких однородных данных много - то тем более имеет смысл создать более компактную версию. 

Например, если вы хотите число 250 записать в один байт как char-тип, вместо того что-бы писать "строкой" в три символа, как то будет делать php по умолчанию, пишем:

pack('c', 250);

Первый аргумент это формат данных по длине. Если все данные char-типа, то можно написать c* как в регулярных выражениях, получить повторение типа. Можно написать c4, что будет аналогично cccc (четыре повторения данных char-типа)

Всего типов данных много..

  • a - строка, свободные места в поле заполняются символом с кодом 0
  • A - строка, свободные места заполняются пробелами
  • h - шестнадцатеричная строка, младшие разряды в начале (little endian)
  • H - шестнадцатеричная строка, старшие разряды в начале (big endian)
  • c - 1 байт (signed char)
  • C - 1 байт (unsigned char)
  • x - символ с нулевым кодом
  • X - возврат назад на 1 байт
  • @ - заполнение нулевым кодом до заданной абсолютной позиции 

Float

  • f - число с плавающей точкой
  • d - число двойной точности 
2 байта (целые, integer)4 байта (long)
- s - short (16 bit)- S - unsigned short- n - short (big endian)- v - unsigned short (little endian)- i - integer (размер и порядок байтов определяется архитектурой)- I - unsigned integer- l - знаковое длинное целое (32 бита, порядок знаков определяется архитектурой)- L - беззнаковое длинное целое- N - беззнаковое длинное целое (32 бита, старшие разряды в конце)- V - беззнаковое целое (32 бита, младшие разряды в конце)

Сам не проверял, но говорят что unsigned int получить не так то просто

6. Распаковка

Для раскодирования используется unpack() с аналогичным форматом типов данных, только теперь можно добавлять названия ключей для результатов ассоциативного массива
unpack("cmessageid/Vtimestamp", pack("H*",FFFF));

Заметьте что pack я тут использую вместо hex2bin, которая недоступна для версий php менее 5.4

7. Конвертирование

base_convert - конвертирование строковых представлений чисел из любого основания в другое (скажем 16 в 2)
bindec, decbin - конвертирование [2-10] двоичных и десятичных данных
octdec, decoct - конвертирование [8-10] десятичных и восьмеричных данных
hexdec, dechex - конвертирование [16-10] десятичных и шестнадцатиричных данных
ord, chr - конвертирование [256-10] десятичных и символьных (ascii) данных

base64_encode, base64_decode - конвертирование [256-256] данных, но в отличие от предыдущих форматов, данные одного значения не кодируются в 6 битах что-бы получить 64 значения, а по прежнему в 8 битах - остальные символы просто не используются.. Из-за этого формат менее эффективен в хранении, но для человека в виде текста более компактен чем нули, единицы или hex.

Отличительная и неприятная особенность, конечно в том что каждая из функций конвертирует данные со строго определённым размером данных. Обычно же у нас есть какой-то пакет данных. Их можно проконвертировать в цикле с определённым шагом..

function str2bin($str, $mode=0, $visual_aid=true) {
$out = false;
for($a=0; $a < strlen($str); $a++) {
$dec = ord(substr($str,$a,1));
$bin = '';
for($i=7; $i>=0; $i--) {
if ( $dec >= pow(2, $i) ) {
$bin .= "1";
$dec -= pow(2, $i);
} else {
$bin .= "0";
}
}
/* Default-mode */
if ( $mode == 0 ) $out .= $bin;
/* Human-mode (easy to read) */
if ( $mode == 1 ) $out .= $bin . " ";
/* Array-mode (easy to use) */
if ( $mode == 2 ) $out[$a] = $bin;

if($visual_aid){
$out.=" ";
}
}
return $out;
}

function str2dec($string, $visual_aid=true){
$hex='';
for ($i=0; $i < strlen($string); $i++) {
$hex .= ord($string[$i]);
if($visual_aid){
$hex.= "(".$string[$i].") ";
}
}
return $hex;
}


function str2hex($string, $visual_aid=true){
$hex='';
for ($i=0; $i < strlen($string); $i++) {
$hex .= dechex(ord($string[$i]));
if($visual_aid){
$hex.= " ";
}
}
return $hex;
}

function hex2str($hex){
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2) {
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
return $string;
}

По теме советую почитать

Профилирование PHP проектов

· 5 мин. чтения

Профилирование это анализ потребления ресурсов при работе программы. Этимология слова видимо связана с тем что профиль это некая граница, отсюда - поиск границ компонентов ПО в ресурсном пространстве. В моём случае программа это исполняемые php-скрипты, а ресурсы это время, память и нагрузка на процессор. Время исполнения не всегда связано с нагрузкой процессора. Процесс может ждать IO-ответа от более медленных источников, а может и просто спать.

Я писал про микрооптимизацию в php. Как правило отмечали критики, в общем случае она не нужна. Профилирование это взгляд с высоты птичьего полёта на подключаемый код. Он делается только на рабочей машинке, а не на production. В некоторых LAMP-сборках инструменты для этого уже установлены.

XDebug

XDebug - это php модуль, я уже давно с ним работаю как с дебаггером.

zend_extension = xdebug.so
xdebug.profiler_enable = 1
xdebug.profiler_aggregate = On
xdebug.profiler_output_dir = /tmp

Он позволяет генерировать доклад о профилировании в форме файла. Из-за глубокой иерархичности, доклад обычно весит несколько мегабайт. Настройки модуля хранятся в php.ini, там же можно и указать путь к результату профилирования. Назовём его cachegrind.out - он совместим с более общим форматом valgrind

Можно сделать так что-бы он генерировался постоянно, можно - с помощью куки-триггеров. Файл можно делать на каждый отдельный системный процесс, на отдельный URL запрос, по времени исполнения или их комбинаций. Дальше его стоит связать с одним из анализаторов:

  1. PHPStorm (Tools - Analyze stacktrace)

  2. webgrind

  3. WinCacheGrind

  4. KCacheGrind 

XHProf

Screen Shot 2012-07-06 at 11.31.19.png

XHProf - пакет PECL, написан в Facebook, поставляется тоже как модуль (xhprof.so или xhprof.dll). Позволяет учитывать значительно больше параметров, в т.ч. нагрузку на CPU и оперативную память. Позволяет сравнивать повторные запуски и агрегировать их результаты. Наиболее полезная фича - суммарный анализ по времени в виде весового графа. Граф генерируется с помощью graphviz и утилитки dot. У меня были небольшие проблемы под маком, пришлось поставить вручную.

pecl install xhprof-0.9.2

Редактируем php.ini:

 extension=xhprof.so
xhprof.output_dir=/var/tmp/xhprof

Поскольку просмотр результатов xhprof целиком web-based, то надо залинковать где-то в корне htdocs путь к xhprof_html папке. Для подключения, необходимо изменять свои скрипты. Т.е. где-то в начале своего index.php ставится include, инициализация и что-бы не бояться за какой-то exit, хук на register_shutdown_function. Вместо того что-бы инклудить каждый раз, можно воспользоваться php-директивой auto_prepend_file и включать это файл всегда

//результат будет виден с ?xhprof=1
if (extension_loaded('xhprof') && $_GET['xhprof']) {
include_once 'xhprof_lib/utils/xhprof_lib.php';
include_once 'xhprof_lib/utils/xhprof_runs.php';
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);

function end_xhprof(){
$profiler_namespace = 'myapp'; // namespace вашего приложения
$xhprof_data = xhprof_disable();
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $profiler_namespace);

//Замените относительный путь к залинкованной папке
$profiler_url = sprintf('http://' . $_SERVER['SERVER_NAME'] . '/xhprof/index.php?run=%s&source=%s', $run_id, $profiler_namespace);

echo 'XHProfiler output';
}

register_shutdown_function('end_xhprof');
}

Можно конечно не делать echo ссылки, а прятать это в javascript-консольку для firebug'а, более подробно советую почитать статью Игоря Бровченко, он даже сравнивал несколько php-фреймворков.. или Лорензо Албертона

Слежка

Профайлеры конечно хороши когда есть начало, конец и результат в виде доклада. Но когда php висит как демон очень долго и хочется понять где же это он тормозит.. В утилитках типа phpcs для вывода деталей работы, часто существует флаг -v (verbose mode). Но до того как я это узнал, мой процесс уже час как висел в jenkins'е и я незнал, стоит ли мне убивать его, или он что-то полезное таки делает. К счастью, в общем случае, можно следить за IO-действиями любого процесса без его согласия с помощью strace:

strace -p IDпроцесса strace php index.php #запустит трейс вместе за запуском php в CLI режиме 

Дебаг зависимостей

PHPStorm умеет искать использование методов и классов, но это статический анализ, а вот для того что-бы динамически построить дерево зависимостей, есть отличный pecl модуль inclued (на момент установки у меня - 0.1.3)

pecl install inclued

Теперь так же редактируем php.ini:

extension=inclued.so
inclued.enabled=1
inclued.dumpdir=/tmp/inclued

Это нам даёт при каждом запросе файл с сериализованным выводом функции inclued_get_data(), которая возвращает все подключённые файлы.

Если следовать мануалу, то по идее так можно сгенерировать png-файлик..

php /usr/lib/php/pear/gengraph.php -i inclued.06567.1
dot -Tpng -o inclued.png inclued.out.dot

..но у меня это просто белый квадратик. Я попробовал другой gengraph.php из репозитория Йориса Бертелота

git clone https://github.com/eexit/Inclued.git /Users/artjom/PhpstormProjects/inclued/ 
sudo php /Users/artjom/PhpstormProjects/inclued/Inclued/gengraph.php -i inclued.07576.1
dot -Tpng -o inclued.png inclued.out.dot
open inclued.png

Увы он оказался малость битым, работал только режим зависимости классов, а не файлов, к тому же только в горизонтальном виде. Пришлось немного попатчить, в частности у dot есть режим rankdir = "LR"; с помощью которого сгенерилась красота на 6 мб.. ужас что так много инклудится, но красиво что так это обнаруживается. 

Это кстати результат загрузки админ-странички у opensource CRM'ки zurmo-stable-0.6.90, в основном там не столько Yii сложен, сколько незамороженный Readbean..

Как оказалось чуть позже, граф зависимостей файлов (режим php gengraph.php -T includes) не работал из-за конфликта с XDebug.

Gephi

Если вам как и мне не нравится что XHProf и Inclued полагаются на graphviz и dot для визуализации, то можно воспользоваться более интересным инструментом - Gephi. Тут надо смотреть как вы хотите это анализировать - можно брать наполовину собираемый .dot файл и импортировать его, а поскольку Gephi не умеет кушать json, то как вариант - парсить исходные данные и генерировать что-то типа CSV

Inclued уже сам генерирует inclued.out.dot в указанной папке, поэтому можно использовать его сразу. На выходе имеем..

Для того что-бы нарисовать картинку XHProf, надо чуть изменить файл callgraph_utils.php, в частности функцию xhprof_get_content_by_run. Она передаёт сгенерированный dot-скрипт напрямую в dot утилиту для генерации картинки. Поэтому просто сохраним его на диск для Gephi.. 

$dotfile = '/tmp/'.time().'.dot';
touch($dotfile);
$fp = fopen($dotfile,'w+');
fwrite($fp,$script);
fclose($fp);

С этим несколько проблем. Первая - это полуфабрикат - данные для dot уже агрегированные, есть только label, куда засунули и время, название метода, число вызовов, проценты и тп. Вторая - файл генерируется только когда открыть генерацию графа в png-формате в браузере

Drupal 7

· 4 мин. чтения

среда, 8 августа 2012 г. в 07:20:52

Drupal 7 это конструктор сайтов (CMF). А это - его краткий обзор на основе того малого опыта что у меня был (так что это скорей статья для меня самого). Седьмая версия уже устарела, скоро готовится выйти 8 версия, частично перенявшая symfony framework

Если обобщить

Основное отличие от CMS - то что схему данных пользователь добавляет сам. Фактически вместо одной таблички БД, часто используется несколько таблиц под каждое поле что с одной стороны даёт гибкие возможности пользователю на лету создавать динамические наборы объектов, давать привилегии на него или его свойства, делать многоязычность и многоверсионность.

С другой стороны это отходит от классического понятия реляционного хранения в MySQL - данные, независимо от типа фактически хранятся как bigtable, а бездумные настройки и импорт данных легко могут привести к размеру таблиц в миллион рядов, при каких-нибудь 20к изначальных элементах. Понятно что это сложно поддерживать, оптимизировать и мигрировать в долгосрочной перспективе при росте.

PHP-процессы при загрузке страниц довольно далеки от оптимального и могут занимать 50-70 мб, что вынуждает активно оптимизировать сайты с кешированием на Varnish и Memcache, перекидыванием поиска на Solr, включением opcode кэширования, Nginx и тп.

Теоретически Drupal поддерживает другие БД типа Mongo, но практически многие модули заточены под MySQL.

В плане frontend всё неплохо - "drupal frontend developer" может работать без хорошего IDE, потому что php-шаблоны генерируются из админки со своими названиями и префиксами в зависимости от типа блоков. Поэтому и возникают  компании, как то из местных - okiamekaiafenomen, банально легко интегрировать дизайн, когда схема тоже сделана в UI.

Терминология

Вот термины которыми оперирует система и стандартный сайт

  1. Content/Entity type концептуально это аналог классов - у них тоже есть свойства (Fields + Properties)

  2. Entities - собственно instance классов

  3. Nodes (+ Article, Users, Comments, Tags) - тоже по сути Entities, экземпляры классов

  4. Views, Regions, Panels. Динамические (могут вызывать методы)

  5. Menus - древовидная структура меню. Элементы могут быть привязаны к странице и её частям. Доступ по URL могут

  6. Modules. Собственно функционал.

  7. Themes. Аналогично модулям, только для стилей (CSS)

Структура админки

  • Content - редактирование статей и прочего содержания клиентом
  • Structure - порядок размещения блоков, изменение типов данных

Структура БД и пример запроса

Центральное место - табличка node, на которой и завязано всё содержимое. Пример запроса..

db_query("SELECT * FROM node LIMIT 5")->fetchField();
fetchObject();

Качество кода

Код - необъектное говно. Нет классов, интерфейсов, пространства имён. Сплошь и рядом используются глобальные переменные, поэтому юнит тестами покрыть его нельзя. Модули состоят из набора обычных функций со своеобразной практикой наименования, например приватные функции имеют _ префикс.  Впрочем, если сравнивать с Joomla, то архитектура более предсказуемая.

Хуки (специально названные функции) меняют данные на лету, если существует функция c похожим именем, проходя по всем модулям. Отсюда и тормоза и гейзенбаги. Например предлагая hook_foo_bar будет вызван baz_foo_bar в модуле baz. 

Есть свой формат для кода, но он не PSR, использует пробелы вместо табов. PHPStorm можно настроить с правилами Code_Sniffer скачав из модуля.

Drush

Drush это консольная утилита, облегчающая рутину, в частности очистку кэша. 

 

Ставится просто, но нуждается в связи с локальным mysql

pear channel-discover pear.drush.org 
sudo pear install drush/drush
sudo ln -s /tmp/mysql.sock /var/mysql/mysql.sock

 

Теперь, находясь в папке проекта можно быстро ставить модули и чистить кэш

drush dl module_name 
drush en module_name
drush cc all

 

Модули

У каждого своя папка с файлами .info и .module. По важности выделяются четыре типа, код лежит в разных местах

  1. core, optional core (/modules)

  2. contrib modules (sites/all/modules),

  3. custom modules (sites/default/modules) + VCS symlink 

Важные модули

- Views- Rules - для ограничений (форм)- Pathauto & token - для ЧПУ- Devel - для дебага- Wysiwyg / inline images- Context- Flags- Entity API- Menu Block- Menu Breadrumb  - Module filter- Chart APi- Features- Feeds- Custom formatters- SmartCrop- File Entity- IMCE- Media - CAPTCHA- OAuth- Entity tokens- Image crop- Job scheduler- Multiblock- Pathauto- Token- Google analytics- Webform- Chaos tools

Дистрибутивы

Почитать

Оптимальные структуры данных в PHP

· 3 мин. чтения

Как известно, родных типов данных в php немного. На самом деле в программировании, типов данных конечно же неограниченное множество, особенно когда дело касается деревьев (AAMk-d и прочая)

Стандартная библиотека php кроме интерфейсов итераторов и автоподгрузки классов предоставляет и более оптимальные типы данных.

Массивы

Что случается когда вы пытаетесь сделать универсальное и "простое" решение? Overhead с памятью. Дело в том что массивы в php на самом деле не массивы. Это сбивает немного с толку, но настоящий массив должен быть таким:

В php же массивы могут расти и итерироваться. Это реализуется списком с двойными ссылками (на следующий и предыдущий элементы). И конечно же массивы ассоциативные - ключи не обязаны быть упорядоченными int'ами - может и строка быть. А это всё усложняет - нужна хэш-таблица, хэш функция, система коллизий. И как следствие дополнительная инфраструктура...

Именно поэтому такой код выдаст не упорядоченный по индексу текст..

$bar = array();
for ($i = 7; $i >= 0; --$i) {
$bar[$i] = $i;
}
echo implode(' ', $bar);

... а то как данные инициализировались: 7 6 5 4 3 2 1 0

Из-за того что новичка не заставляют думать о структуре данных, как то делают в Java, выходит неоптимальное использование памяти. Для поддержки таких низкоуровневых массивов есть SplFixedArray который на 25-40% быстрей массивов, а также SplStack с SplQueue. 

Структуры

Из-за своей простоты и лёгкой инициализации, массивы стали типичными контейнерами данных - их возвращает какой-нибудь адаптер БД как результат, его передают между собой методы где одним параметром не обойтись, он же итерируется или распаковывается в шаблонизаторах. 

Но я в своём движке использую объекты. Начиная с 5.4, объекты легче чем массивы. Из-за фиксации параметров классов и большей строгости, возможны подсказки при работе в IDE. Я умолчу об ООП - дополнительный методы у контейнеров могут добавить больше сложности чем решить.

Множества

Когда порядок не имеет особого значения, можно использовать множества. Конечно с массивами можно проводить такие же операции сложения (array_merge), вычитания (array_diff), пересечения (array_intersect) и проверки на присутствие (in_array), но как вы уже поняли, поиск по значению довольно медленный и вместо этого следует использовать поиск по ключу. Особенно если вы храните объекты:

$mySet = array();
$mySet(spl_object_hash($obj1)) = $obj1;
$mySet(spl_object_hash($obj2)) = $obj2;
isset($mySet(spl_object_hash($obj1))); //проверка вместо in_array
$mySet + $yourSet; //сложение работает потому что нет коллизий ключей
array_intersect_key($mySet, $yourSet); // пересечение по ключу
array_diff_key($mySet, $yourSet); // вычитание

Куча

Heap это просто такое дерево, ключи элементов которого распределяются по старшинству. Кучи часто используются в алгоритмах с графами. В php для этого есть SplHeap, SplMaxHeap и SplMinHeap, которые в сотни раз быстрей массивов.

Обобщение кучи это очередь с приоритетом - SplPriorityQueue, где порядок не FIFO/LIFO, а как вы догадались, согласно заданной важности

$priorityQueue = new SplPriorityQueue();
$priorityQueue->insert("Putin", 60);
$priorityQueue->insert("Zjuganov", 15);

Статический и динамический анализ php-кода

· 4 мин. чтения

Автоматический анализ кода (static code analysis) очень полезен на больших проектах и он часто встраивается в серверы непрерывной интеграции. Некоторые IDE уже поставляются с простыми аналитическими инструментами, но первые всё-таки предпочтительней, потому что туда смотрит вся комманда. Всё что этот софт делает это периодически смотрит в систему версионирования (SVN) и строит график качества (и например запускает юнит-тесты). По сути это аналог комплекса упражнений для человека, поддерживающих хорошее здоровье и бъющих тревогу если возникает рак спагетти-кода.

Самые известные CI-серверы:

Статический анализ кода и его метрики

В целом, статический анализ кода сводится к оценке метрик кода с поиском потенциальных проблем. Начиная с банальных необъявленных или не использующихся переменных и заканчивая поиском дубликатов. Метрики позволяют оценить цену и сложность проекта для планирования дальнейших работ, позволяют оценить влияние от новых методологий и инструментов, уменьшают время поиска проблемных мест как по пакетам так и по разработчикам.

Метрик очень много и каждый аналитический инструмент по разному их формулирует и сокращает, поэтому единой картинки добиться сложно, хотя и есть закономерность в шагании по стопам java

Размерные метрики

  • NOP - число пакетов
  • NOC - число классов → Число классов в пакете (Java =17, C++=19)
  • NOM - число методов → Число методов в классе (Java = 7, C++ = 9)
  • LOC - число строк → Ошибок/KLOC, Документации/KLOC
  • IOp - число параметров входа/выхода
  • IOg - число переменных вызываемых методом (классовых и глобальных, не локальных)
  • IOvar = IOp + IOg
  • CS - число атрибутов и методов. Может указывать на слишком большую ответственность одного класса
  • NOO - число переписанных родительских методов в дочерних классах. Указывает на высокую или низкую абстракцию
  • NOA - число новых методов в зависимости от глубины наследования

Метрики наследования

  • ANDC - среднее число наследующих классов (Java = 0.41, C++ = 0.28)
  • AHH - средняя высота иерархии (Java = 0.21, C++ = 0.13)
  • MIF = число унаследованных но не перезаписанных методов / общее число методов, указывает на степень абстракции или специализации класса (значение от 0 до 1)
  • PF - фактор наследования = число наследуемых методов / (число новых методов * число дочерних классов ), показывает насколько используются наследуемые методы (значение от 0 до 1)

Метрики сложности

  • CF - фактор связанности двух классов (т.е. один класс вызывает или использует методы/свойства другого класса) = число вызовов / максимальное число вызовов (значение от 0 до 1)
  • CALLS - число вызовов методов
  • FANOUT- число (вызываемых ) классов → (fin+fout)2 * len
  • Структурная сложность = CALLS 2
  • Сложность данных SC = IOvar / (CALLS +1)
  • Цикломатическая сложность CC = число решений / число строк (Java = 0.2, C++ = 0.25). Разные научные определения (Halstead, McCabe, McClure)
  • Системная сложность: SYSC = SC + SD

Некоторые программы ещё измеряют степень безопасности через число точек прямого входа, т.е. использования параметров GET, POST, SESSION, FILE. 

Инструменты

Для php таких аналитических программ относительно мало — есть консольные узкоспециализированные программки:

  • Depend - использует некоторые приведённые выше метрики для анализа сложности pdepend --overview-pyramid=out.svg my_project_namepdepend --jdepend-chart=out2.svg my_project_name pdepend --jdepend-xml=out.xml my_project_namedependencies.php out.xml -o out3.svg

  • Mess detector - ищет неиспользуемый код и сложные выражения phpmd my_project_name text phpmd.xml

  • Code sniffer - проверяет названия методов и переменных согласно правилам из XML-файла настроек phpcs --standard=CodeREview --report-source my_project_name

  • Dead code detector ищет невызываемый код

  • Copy-paste detector

  • phploc - оценивает размер
    phploc my_project_name

  • PHPLint - проверяет синтаксис и генерирует документацию

  • Analzer for Type Mismatches - ищет возможные ошибки с несовместимостью типов

Ещё есть чуть более общие PMD (на java) и phpsatспециализированные RIPSRATS (как вариант Fortify360 + Jenkins), YascaPixy и агрегаторы всего что только можно - PHPUnit,  PHPLintSonar

Динамический анализ кода

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

  • Покрытие кода тестами (Statement, Branch, Path coverage)
  • Граф скорости загрузки и использования памяти в зависимости от методов

Инструменты

Анализ ошибок с XDebug и PHPStorm 2.0

· 2 мин. чтения

XDebug это отличный php-модуль для правильного дебага приложения, который в «старших» языках (читай - не интерпретируемых) уже сразу был встроен в компилятор. Необходимость в полноценном дебаге очевидная в сложных приложениях, где воспроизведение ошибки занимает относительно много времени, а объём данных не позволяет копаться в мегабайтах от print_r(), хотя этот модуль позволяет и такие отчёты

Посколько xdebug это модуль, то не на всяком shared-хостинге он имеется и поэтому подразумевается что разработчик подымет у себя php+xdebug сам. После этого в php.ini включается модуль и его настройки по умолчанию. Заметьте что remote_host по IP ограничивает число работающих с дебагерром.

extension=C:\Program Files\php\ext\php\xdebug-2.1.0-5.3-vc9.dll
xdebug.profiler_enable = 1
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.remote_handler=dbgp
xdebug.idekey=

Для правильного дебага надо соединить IDE и модуль Xdebug, что-бы последний остановил работу php и передал данные всех переменных в PHPStorm:

  1. Включить прослушивание 9000 порта в PHPStorm и режим дебага в IDE через Shift+F9 или из меню Run/Debug.

  2. Прописать в куки режим дебага что-бы X-debug на удалённом сервере понял когда ему стоит работать. Jetbrains сделали генератор букмарков для простой работой с печеньками. Второй вариант - специальные плагины для браузеров. Параметр Ide key вводится такой же как и в настройках IDE и в php.ini

Теперь можно поставить breakpoint на любой строке. Проблемы начинаются когда данные таки начинают бегать -  если удалённый сервер на линуксе, а у вас винда то естественно пути которые на линуксе не совсем соответсвуют той иерархии кода которую видет PHPStorm. Благо эта проблема решается маппингом папок на нужное место. Вторая проблема - закрытый код. XDebug как ни в чём не бывало выдаёт все пути что запускалось.. а IDE ведь не может этого проверить.

Наконец главная проблема - как выкидывать trace автоматически при возникновении ошибки, без установки breakpoint? Я частично решил для себя эту проблему используя свой регистратор ошибок вместе с xdebug_break(), проблема в том что фатальные ошибки не до конца показывают stack trace.

function ErrorHandler($errno, $errstr, $errfile, $errline) {
if (!in_array($errno,array(E_NOTICE,2048))) {
xdebug_break();
restore_error_handler();
trigger_error($errno.$errstr." in ".$errfile." on line ".$errline."; showed by error handler ");
}
}

function shutDownFunction() {
if(!is_null($e = error_get_last())){
xdebug_break();
}
}

function exceptionHandler($exception) {
xdebug_break();
restore_exception_handler();
}

set_error_handler('ErrorHandler');
register_shutdown_function('shutdownFunction');
set_exception_handler('exceptionHandler');

PHP frameworks

· 5 мин. чтения

суббота, 10 октября 2009 г. в 23:32:55

Как можно уже заметить в моём блоге, я уделяю дотошное внимание используемому языку и смыслу слов, поэтому термин фреймворк мне не нравится, вместо этого по-моему правильней говорить о каркасе или скелете приложения.

Выбор современных каркасов для php достаточно широк. Меня всегда интересовала именно внутренняя структура запуска типичного приложения, поэтому постараюсь описать их именно с этой стороны. 

Codeigniter 1.7.6 - легковесный (1 mb) и простой в освоении. За основу берётся MVC-паттерн, популяризированная в ruby on rails. Основное приложение лежит в application папке, делится в свою очередь на контоллеры, шаблоны и модели, кроме того папки - errors, helpers, language, config и hooks.  Классы контроллеров соответсвенно наследуют Controller и по умолчанию запускается index-метод, шаблоны показываются через this->load->view(имя_шаблона, передаваемые_переменные).

Из полезного замечены классы валидации форм, работы с изображениями (imagemagick, watermark), trackback-запросов для статей в блогах, автогенерация кода (scaffolding). Само собой поддержка разных бд (mssql, mysql, postgre, oracle, sqlite). Итого получается достаточно типично - одно приложение, разбитое по контроллерам. Идеальный фреймворк для минималистов типа меня.

Сайт очень позитивный - вики, видео примеры, форумы и багтрекер.

Symfony 1.3a от Sensio Labs позиционируется как более тяжёлый каркас (15 mb!). Та же MVC, но теперь с использованием ORM (Propel или Doctrine). Из ещё полезных фишек - кеширование шаблонов, scaffolding, многоязычность, чпу, недоступность исполняемых файлов от публичного каталога апача (как следствие - безопасность), настройка среды где проект вертится (development, testing, production). В минусы я бы отнёс привязку к JS библиотеке (Prototype), достаточная тяжеловесность по оперативке и времени из-за отсутсвия lazy loading классов (sic!) и опять же ORM.

Ещё одной особенностью является использование YAML в качестве файлов настроек. Если смотреть пример установки тестового (sandbox) приложения то видно что есть работа с консолью по автогенерации файлов моделей. Приложения в свою очередь состоят не только из моделей (обеспечивающим интерфейсы к бд), но и из модулей (с CRUD-действиями и интерфейсами).

По запуску процесса мне показалось очень сложным. Надо прописывать настройки в .yml файлах, что-бы сгенерировать всей файлы. Потом по идее грузится front controller, который вызвает модуль и в нём уже метод (по умолчанию - executeIndex) и вызывает шаблон indexSuccess.php

Сайт тоже достаточно путанный и быстрого погружения не даёт, а если распечатать примеры создания приложения, то из них получится более 10 страниц текста.

CakePHP 1.2.6 позиционируется как клон RoR и пожалуй с него слизан был Codeigniter (в том числе и поддержка старого php 4 ). Понимание как работает CakePHP это по сути понимание как работают остальные похожие каркасы или почему они работают неправильно. Почему именно это решение не стало очень популярным - сложно сказать, может дело в названии и не все такие сладкоежки.

"Пирожок" в общем быстрей чем Codeigniter и Yii, весит 7 мб и в общем предполагает тоже определённые названия классов (AppController, AppModel). В шаблон данные из контроллера передаются через $this->set(). Что понравилось - есть возможность юнит тестирования, очистки данных (Sanitize::paranoid), автогенерации кода (Cake bake), использования модели как дерева (и его показ методом generatetreelist)

Zend Framework 1.9 скачать проблематичней - надо регистрироваться на сайте этой Zend-компании которая и имеет то преимущество что выпускает свой IDE, Optimizer и сервер. IDE у них глючный, поэтому к каркасу приложений я так же скептически отношусь. И не зря - этот монстр выпускается в двух версиях и минимальная из них весит 20 мб! Названия классов всюду фигурируют с Zend префиксом. В общем сразу видно отношение к разработчикам.

В плюсы - большая независимость классов. Можно взять тот же Zend_DB и использовать только его в своём проекте, подобно библиотеке PEAR, ещё в Zend_DB уже есть выборка данных методами fetchPairs, fetchAll (которые я давно использую в своём движке), переводы в зависимости от числительности (Zend_Translate::plural), интеграцию с PHPunit, разделение на приватный и публичный каталог, ограничение прав (Zend_Acl).

По структуре.. Контроллеры наследуют Zend_Controller_Action и по умолчанию запускают indexAction метод. Данные в шаблон передаются через переменную контроллера $this->view. Шаблоны хранятся в .phtml файлах.

Одновременно и в плюсы и в минусы можно записать наличие обработки форм и веб-сервисов типа youtube. Не понравилось что в Zend_PDF очень низкоуровневые способы генерации файла, отсутсвие таблиц. В минусы я запишу и интеграцию с Dojo, общую тяжеловесность и дух рационального корпоративизма.

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

Akelos в основном продвигает Bermi Fermer и сразу признаётся в том что это максимально полный клон RoR и весит приличные 9 мб. Запуск очень аналогичен вышеназванным каркасам, поэтому я не удивляюсь что контроллер наследует ApplicationController и по умолчанию тоже вызывается index-метод. Переводы в контроллере можно получить через AK::t() метод, что странно и непропорционально относительно названий других объектов и методов.

Данные в шаблон летят через $this->saveAttribute(). Вобщем ничего отличительного что вы в руби не видели. Интересная выборка с использованием ActiveRecord и множественностью ассоциаций между моделями (has_one, belongs_to). Использование AdoDB в качестве адаптера к базе данных, хранение сессий в базе.

Минусы - интеграция с Prototype.js и scriptaculous, отсутствие документации, хранение переводов в ассоциативном массиве, практически нулевая популярность и сообщество разработчиков. Вряд ли пока стоит брать на вооружение.

Yii PHP Framework 1.0.9 на удивление хорошо документируется в рунете, целиком объектный и хвастающийся что он быстрей даже чем CodeIgniter, хотя скорость по идее зависит от многих вещей кроме скелета. Развился из Prado, весит неплохие 7 мб.

По запуску - вызывается контроллер который наследует CController, потом в зависимости от запрашиваемого URL вызывается более низкий класс который наследует CAction. Для всяких дополнительных проверок на права можно использовать фильтры - CFilter. Модели могут быть двух типов - Active Record либо CFormModel. Шаблоны показываются в контроллере через $this->render().

Префикс "C" как и с "Zend" даёт какую-никакую совместимость но и добавляет шума. В целом мне показался каркас удобней чем Symfony, но чуть более корпоративный чем CodeIgniter.

Читайте также

Статический и динамический анализ php-кода

· 2 мин. чтения

PEAR - вполне неплохая php-библиотека, хоть и достаточно тяжёлая. Определённо и в других библиотеках имеются классы работающие с рассылкой почты. А рассылка почты системой должна как правило отличатся от обычной mail() функции. Более того, модули рассылок почты зарегистрированным пользователям обычно самые сложные. И вопрост не столько в том что я ниже описываю как отсылается почта, а в том кому и в каком виде это делается.

При работе с таким модулем обычно делают такие основные регистры (понятия, коренные таблицы, классы если хотите):

  • Письмо которое включает заголовок, содержание (наверно на нескольких языках)
  • Рассылка (кому какое письмо надо отослать и прогресс этого)
  • Пользователи (не обязательно из таблицы зарегистрированных пользователей, может быть вполне список почтовых адресов)

Дальше можно усложнять тем что можно выбрать пользователей по каким-нибудь параметрам (возраст, язык, пол, группы и тп). Можно добавить слежение за тем что пользователь прочитал письмо (вставить картинку с сайта). Обязательна функция отказа от рассылки. Это наверно все знают, поэтому делюсь кодом который часто использую для собственно процесса отсылания почты.

Ещё одна особенность при авторассылке это шаблонная замена имени, ссылок, дат, цен - всего того что пользователи с издёвкой отмечают корпоративной безжизненностью. Эти замены должны быть достаточно гибкими что-бы пользователи не заметили что они дешёвый материал; Нельзя рассылать письма типа "Дорогой Ира Кузнецова", или "Дорогой Artjom.." - надо обращать внимание на язык, пол, временную зону, кодировку имени пользователя. Умную рассылку не так легко написать, но сегодня я не об этом, а только о части - о картинках.

Есть два типа внедрения картинок - со внешними ссылками (помоему наиболее приемлимый), либо с картинками которые прикреплены как ариложения (attachment). Этот второй вариант значит что картинки покажут сразу (пользователю не надо подтверждать доверие ко внешним ссылкам), но в то же время некоторые email-сервисы типа google всё-равно внизу страницы уродливо показывают эти картинки отдельно как приложения, тогда как Outlook жуёт нормально.


require_once('Mail.php');  
require_once('Mail/mime.php');
                
$mime = new Mail_mime("\n"); $mime->addHTMLImage(sys_url.'img/stripe.gif', 'image/jpeg');
$mime->addHTMLImage(sys_url.'img/logo.gif', 'image/jpeg');       
$mime->_build_params['html_charset']='UTF-8';
$mime->_build_params['text_charset']='UTF-8'; $mime->setTXTBody($arrTemplate['txt']);
$mime->setHTMLBody($strWrappedHTML);
        
$body = $mime->get();
$hdrs = $mime->headers(array('From' => $arrTemplate['from'], 'Subject' => $arrTemplate['subject']));
        
$mail =& Mail::factory('mail');
$mail->send($arrParams['email'], $hdrs, $body);

Если будете интегрировать, гляньте баги 1436 и 12216 тоже

CSV - кодировки, импорт и экспорт

· 2 мин. чтения

Comma Separated Values - типичный способ экспорта табличных данных, но далеко не стандартный в плане совместимости между Excel, php и другими источниками данных. Надо читать RFC, и пробовать всё на практике

Самое наглядное - если вы думали что данные достаточно через запятую перечислить, то если вы сохраните простой текстовый файл с такими данными:

ID,name 1,Mac'duck

То в Excel получите

Тому несколько причин

  • Отсутсвие utf8
  • Разные символы разделения данных (табы вместо запятых)
  • Особые символы (", n , r) должны очищаться что-бы не побить линейную разметку
  • fgetcsv()
  • Данные лучше помещать в двойные кавычки, а сами двойные кавычки в данных эскейпятся повторением:
    $value = str_replace('"', '""', $value);
    $value = '"' . $value . '"' . ",";

Mysql

SELECT id, name, email INTO OUTFILE '/tmp/result.csv'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY 'n'
FROM users WHERE 1

 

Экспорт

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: public');
header('Content-Description: File Transfer');
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename=SomeFile_' .time(). '.csv;');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.strlen($strCSV));

mb_internal_encoding("UTF-8");

$strCSV= chr(255) . chr(254) . mb_convert_encoding($strCSV, "UTF-16LE", "UTF-8");

 

Импорт

uiyi

$sCSVFileData = file_get_contents($aFile['tmp_name']);
if (mb_detect_encoding($sCSVFileData)) {
$sCSVFileData = mb_convert_encoding(mb_substr($sCSVFileData, 2, mb_strlen($sCSVFileData)), "UTF-8", mb_detect_encoding($sCSVFileData));
}
else {
$sCSVFileData = mb_convert_encoding(mb_substr($sCSVFileData, 2, mb_strlen($sCSVFileData)), "UTF-8", "UTF-16LE");
}

$aCSVLines = explode("rn", $sCSVFileData);

for ($i = 0; $i < count($aCSVLines); $i++) {
if ($i > 0) {
$arrData[] = getCSVValues($aCSVLines[$i], "t");
}
}

function getCSVValues($sString, $sSeparator = ",") {
$sString = str_replace('""', "'", $sString);
$aBits = explode($sSeparator, $sString);
$aElements = array();
for ($i = 0; $i < count($aBits); $i++) {
if (($i % 2) == 1) {
$aElements[] = $aBits[$i];
}
else {
$sRest = $aBits[$i];
$sRest = preg_replace("/^" . $sSeparator . "/", "", $sRest);
$sRest = preg_replace("/" . $sSeparator . "$/", "", $sRest);
$aElements = array_merge($aElements, explode($sSeparator, $sRest));
}
}

return $aElements;
}

Пространство имён в php 5.3 и php 6

· 2 мин. чтения

Пространство имён (namespace/package) знакомо java и c# программистам, теперь доступно и в php. Нужно оно для того, что-бы не писать длинные перефиксы к названиям классов, как сейчас делается в Zend, PEAR и других библиотеках и платформах для совместимости.

Вместо этого классы, функции, интерфейсы (абстрактные классы) и константы могут быть объединены в одно пространство имён. Глобальные переменные в это пространство не входят.

Простой пример

Определяется пространство ключевым словом namespace

//определяем класс в пространстве namespace MyCMS::Core; class System{}

Использование этого класса в третьих библиотеках осуществляется ключевым словом use

require_once('mycms/core.php'); use MyCMS::Core::System; //импортируем только заданный класс $objSystem=new System;

Как вы уже догадались, разделитель :: работает так же как и вызов статических методов, разделяя уровень пространства имён и конкретный класс. Уровень вложённости пространств можно использовать наряду с модульностью библиотеки, например реализовать пространства Database::MySQL::Adapter и Database::Oracle::Adapter.

Новые возможности и новые проблемы

Если в пространстве имён имя класса перезаписывает ранее объявленный глобальный класс или функцию, то к нему по прежнему можно обратится через ::SomeClassName, однако не зная к какой именно реализации программист обращается может вызвать проблемы. Например можно переобъявить стандартные php функции, поменять sin() и cos() местами, что-бы кому-то жизнь мёдом не казалась.

Для отладки существует рабочая константа NAMESPACE.

С двоеточием возникает и проблема неоднозначности:

A::B() // вызов функции B из namespace A A::B() // вызов статичного метода B у класса A

Читайте по теме:

Календарь на PHP, MySQL и ajax

· 2 мин. чтения

Для корпоративных сайтов зачастую нужен календарь. И ладно бы что-бы он просто показывал дни недели, так зачастую вносится понятие События (Event), а ввиду обширного потенциала этого понятия можно говорить об Event Management System. Поскольку деловым людям свойственно планирование, то такая система должна быть удобной, мобильной и расширяемой. За удобство отвачает ajax, за мобильность - само наличие интернета, а за расширяемость - программист и проектировщик.

А ведь событие ещё может иметь и..

  • повторяемость (с определённым периодом , до бесконечности/определённого числа повторений/конкретной даты )

  • местоположение (для точного - google maps, для неточного - просто текст)

  • привязку к временной зоне

  • неопределённое время или длительность (идёт в todo список?)

  • принадлежать группам или типам событий (деловые встречи, личные встречи, культурные мероприятия)

  • доступ к просмотру для других пользователей (sharing, оповещение)

  • систему оповещения по email/sms/desktop widget


Публичные сервисы

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

Opensource-продукты

Когда дело касается своего сайта, то выбор значительно уже:

  • PlansCalendar на php/mysql, без ajax и видов по дням/неделям

  • phpiCalendar внешне обманчив - работает на файловой системе с .ics файлами

  • monket хоть и на ajax, но скуден

  • thyme платен и по сути чуть лучше чем PlansCalendar

  • jquery fullcalendar - оболочка без серверной части

  • jquery wdcalendar

  • myCalendar

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

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

Сортировка матрицы

· 1 мин. чтения

Сортировку можно производить на стороне сервера в БД (самое логичное решение), но зачастую система не продумана заранее настолько и получается что данные приходится выдирать из разных таблиц, между которыми нет очевидных связей. Поэтому сортировать приходится через серверные технологии или даже через javascript на стороне клиента..

Средствами PHP насколько мне известно пока невозможно нормально отсортировать - функции uksort и ksort сортируют по ключам массива, но просто вписать данные столбца в качестве ключа нельзя если могут появится одинаковые ключи.

Небольшая функция на будущее.

  /**
* Sorts matrix by column
*
* @param array $arrMatrix
* @param mixed $col
* @param boolean $direction
* @return array
*/
function sort_matrix($arrMatrix,$col=0,$direction=1){
$result=array();
foreach($arrMatrix as $var => $val){
$set=false;
foreach($result as $var2 => $val2){
if($set==false){
if($val[$col]>$val2[$col] && $direction || $val[$col]<$val2[$col] && !$direction){
$temp=array();
foreach($result as $var3 => $val3){
if($var3==$var2) $set=true;
if($set){
$temp[$var3]=$val3;
unset($result[$var3]);
}
}
$result[$var]=$val;
foreach($temp as $var3 => $val3)
$result[$var3]=$val3;
}
}
}
if(!$set) $result[$var]=$val;
}
return $result;
}

Сохраняющаяся сортировка

· 1 мин. чтения

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

Представляю небольшой вариант такого решения..

$_SESSION['Sorting'][get_class($this)]= array_merge( $_SESSION['Sorting'][get_class($this)], $_REQUEST);

В сессии кэшируются все параметры некогда используемые, причём новые параметры перезаписывают более старые. С такой техникой можно делать много параметровую сортировку ( ORDER BY param1, param2, param3..) причём передавать через GET только по одному параметру, т.е. не надо делать

?sort[]=param1&sort[]=param2