Случайны выбор дневника Раскрыть/свернуть полный список возможностей
";
echo "";

if($enableUseiconv==1)
$line[0]=iconv("CP1251","UTF-8",urldecode($line[0]));

echo "";

И вместо последней строки вставить следующее:



//Проверяем, HTTPS ресурс в строке или нет (по наличию символа ':')
//Если символа нет, значит это HTTP ресурс, сразу отображаем на страницу
$dv=strpos($line[0], ":") ;
if ($dv < 1) {
echo "
";
} else
{

// Если же все таки символ ':' присутствует, следовательно это HTTPS ресурс, значит
// производим "колдовские" действия...

// Отделяем IP адрес от всей строки, т.е. все символы до ':'
$str1=strpos($line[0], ":");
$row1=substr($line[0], 0, $str1);
$ipaddress = ltrim($ipaddress);
$ipaddress = $row1;

// Производим резолв IP адреса с помощью скрипта gethost.sh
$hostname = shell_exec('/usr/bin/gethost.sh ' . $ipaddress);

// Выводим в таблицу полученную информацию об IP адресе
echo "
";
}


Код писАлся на скорую руку, но вполне работает. А срабатывает он, когда открывается просмотр отчета «Трафик пользователей IP адреса», лично мне по большей части необходим только такой отчет. При желании, можно добавить подобный код на любые другие отчеты.



Сам код довольно прост: сначала определяется, какой в данный момент ресурс выводится на экран в таблицу: HTTP или HTTPS, и если это HTTPS (определяется по наличию символа ":"), то отделяем IP адрес от порта, передаем IP адрес в скрипт gethost.sh, получаем вывод скрипта в виде информации об IP адресе, и выводим на экран.



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



Ах да, чуть не забыл, скрипт должен быть на том же сервере, где расположен парсер Screen Squid. Ну это так, к слову.



Если есть предложения по улучшению, доработке, переделке данного скрипта, буду рад выслушать.
Original source: habrahabr.ru.

https://habrahabr.ru/post/307686/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best



Найдено 6774 сообщений
Cообщения с меткой

php - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

[Перевод] Массивы в РНР 7: хэш-таблицы

Понедельник, 22 Августа 2016 г. 19:17 (ссылка)

Хэш-таблицы используются везде, в каждой серьёзной С-программе. По сути, они позволяют программисту хранить значения в «массиве», индексируя его с помощью строк, в то время как в языке С допускаются только целочисленные ключи массива. В хэш-таблице строчные ключи сначала хэшируются, а затем уменьшаются до размеров таблицы. Здесь могут возникать коллизии, поэтому нужен алгоритм их разрешения. Существует несколько подобных алгоритмов, и в РНР используется стратегия связных списков (linked list).



В Сети есть немало замечательных статей, подробно освещающих устройство хэш-таблиц и их реализации. Начать можно с http://preshing.com/. Но имейте в виду, вариантов структуры хэш-таблиц — несметное множество, и ни один из них не совершенен, в каждом есть компромиссы, несмотря на оптимизацию циклов процессора, использования памяти или хорошее масштабирование потокового окружения (threaded environment). Одни варианты лучше при добавлении данных, другие — при поиске и т. д. Выбирайте реализацию в зависимости от того, что для вас важнее.



Хэш-таблицы в РНР 5 подробно рассмотрены в материале phpinteralsbook, который я написал вместе с Nikic, автором хорошей статьи про хэш-таблицы в РНР 7. Возможно, её вы тоже сочтёте интересной. Правда, она писалась до релиза, поэтому некоторые вещи в ней слегка отличаются.



Здесь же мы подробно рассмотрим, как устроены хэш-таблицы в РНР 7, как с ними можно работать с точки зрения языка С и как ими управлять средствами РНР (используя структуры, называемые массивами). Исходный код в основном доступен в zend_hash.c. Не забывайте, что хэш-таблицы мы используем везде (обычно в роли словарей), следовательно, нужно проектировать их так, чтобы они быстро обрабатывались процессором и потребляли мало памяти. Эти структуры решающе влияют на общую производительность РНР, поскольку местные массивы не единственное место, где используются хэш-таблицы.



Конструкция хэш-таблицы



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




  • Ключ может быть строкой или целочисленным. В первом случае используется структура zend_string, во втором — zend_ulong.

  • Хэш-таблица всегда должна помнить порядок добавления её элементов.

  • Размер хэш-таблицы меняется автоматически. В зависимости от обстоятельств она самостоятельно уменьшается или увеличивается.

  • С точки зрения внутренней реализации размер таблицы всегда равен двойке в степени. Это делается для улучшения производительности и выравнивания размещения данных в памяти.

  • Все значения в хэш-таблице хранятся в структуре zval, больше нигде. Zval’ы могут содержать данные любых типов.



Рассмотрим структуру HashTable:



struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar reserve)
} v;
uint32_t flags; /* доступно 32 флага */
} u;
uint32_t nTableMask; /* маска — nTableSize */
Bucket *arData; /* полезное хранилище данных */
uint32_t nNumUsed; /* следующая доступная ячейка в arData */
uint32_t nNumOfElements; /* общее количество занятых элементов в arData */
uint32_t nTableSize; /* размер таблицы, всегда равен двойке в степени */
uint32_t nInternalPointer; /* используется для итерации */
zend_long nNextFreeElement; /* следующий доступный целочисленный ключ */
dtor_func_t pDestructor; /* деструктор данных */
};


Некоторые поля используются редко, и поэтому мы о них говорить не будем.



Размер этой структуры — 56 байт (согласно модели LP64).



Самое интересное поле данных — arData, это своеобразный указатель на область памяти цепочки Bucket. Сама Bucket представляет собой одну ячейку в массиве:



typedef struct _Bucket {
zval val; /* значение */
zend_ulong h; /* хэш (или числовой индекс) */
zend_string *key; /* строковый ключ или NULL для числовых значений */
} Bucket;


Как вы могли заметить, в структуре Bucket будет храниться zval. Обратите внимание, что здесь используется не указатель на zval, а именно сама структура. Так сделано потому, что в РНР 7 zval’ы больше не размещаются в куче (в отличие от PHP 5), но при этом в РНР 7 может размещаться целевое значение, хранящееся в zval в виде указателя (например, строка РНР).



Давайте посмотрим на картинке, как происходит размещение в памяти:







Как видите, помещаемые в хэш-таблицу данные хранятся в смежном разделе памяти: arData.



Добавление элементов с сохранением очерёдности



РНР должен сохранять очерёдность элементов при их добавлении в массив. Если применить к массиву инструкцию foreach, то вы получите данные именно в том порядке, в котором они помещались в массив, вне зависимости от их ключей:



$a = [9=>"foo", 2 => 42, []];
var_dump($a);

array(3) {
[9]=>
string(3) "foo"
[2]=>
int(42)
[10]=>
array(0) {
}
}


Это важный момент, наложивший ряд ограничений при реализации хэш-таблиц. Все данные размещаются в памяти рядом друг с другом. В zval’ах они хранятся упакованными в Bucket’ы, которые располагаются в полях С-массива arData. Примерно так:



$a = [3=> 'foo', 8 => 'bar', 'baz' => []];






Благодаря этому подходу можно легко итерировать хэш-таблицу: достаточно всего лишь итерировать массив arData. По сути, это быстрое последовательное сканирование памяти, потребляющее очень мало ресурсов процессорного кэша. В одной строке могут помещаться все данные из arData, а доступ к каждой ячейке занимает около 1 наносекунды. Обратите внимание: чтобы повысить эффективность использования процессорного кэша, arData выравнен в соответствии с 64-битной моделью (также используется оптимизирующее выравнивание 64-битных полных инструкций). Код итерирования хэш-таблицы может выглядеть так:



size_t i;
Bucket p;
zval val;

for (i=0; i < ht->nTableSize; i++) {
p = ht->arData[i];
val = p.val;
/* можно что-нибудь сделать с val */
}


Данные сортируются и передаются в следующую ячейку arData. Для выполнения этой процедуры достаточно просто помнить следующую доступную ячейку в этом массиве, хранящуюся в поле nNumUsed. При каждом добавлении нового значения мы помещаем его ht->nNumUsed++. Когда количество элементов в nNumUsed достигает количества элементов в хэш-таблице (nNumOfElements), мы запускаем алгоритм уплотнения или изменения размера (compact or resize), о котором поговорим ниже.



Упрощённое представление добавления элементов в хэш-таблицу с использованием строковых ключей:



idx = ht->nNumUsed++; /* берём номер следующей доступной ячейки */
ht->nNumOfElements++; /* инкрементируем количество элементов */
/* ... */
p = ht->arData + idx; /* получаем в этой ячейке bucket от arData */
p->key = key; /* устанавливаем ключ, перед которым собираемся вставить */
/* ... */
p->h = h = ZSTR_H(key); /* сохраняем в bucket хэш текущего ключа */
ZVAL_COPY_VALUE(&p->val, pData); /* копируем значение в значение bucket’а: операция добавления */


Стирание значений



При удалении значения массив arData не уменьшается и не перегруппируется. В противном случае производительность бы катастрофически упала, поскольку нам бы приходилось перемещать данные в памяти. Поэтому при удалении данных из хэш-таблицы соответствующая ячейка в arData просто помечается специальным образом: zval UNDEF.







Следовательно, итерационный код нужно немного перестроить, чтобы он мог обрабатывать такие «пустые» ячейки:



size_t i;
Bucket p;
zval val;

for (i=0; i < ht->nTableSize; i++) {
p = ht->arData[i];
val = p.val;
if (Z_TYPE(val) == IS_UNDEF) {
continue;
}
/* можно что-нибудь сделать с val */
}


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



Хэширование ключей



Ключи необходимо хэшировать и сжимать, затем преобразовывать из сжатого хэшированного значения и индексировать в arData. Ключ сожмётся, даже если он целочисленный. Это необходимо для того, чтобы он вписался в границы массива.



Нужно помнить, что мы не можем индексировать сжатые значения как есть напрямую в arData. Ведь это будет означать, что использованные для индекса массива ключи прямо соответствуют ключам, полученным из хэша, что нарушает одно из свойств хэш-таблиц в РНР: сохранения порядка элементов.



Например: если добавить сначала по ключу foo, а потом по ключу bar, то первый будет хэширован/сжат до ключа 5, а второй — до ключа 3. Если данные foo хранить в arData[5], а данные bar — в arData[3], то получится, что данные bar идут до данных foo. И при итерировании arData элементы будут передаваться уже не в том порядке, в котором они добавлялись.







Итак, мы хэшируем, а потом сжимаем ключ, чтобы он поместился в отведённые границы arData. Но мы не используем его как есть, в отличие от PHP 5. Необходимо сначала преобразовать ключ с помощью таблицы преобразования (translation table). Она просто сопоставляет одно целочисленное значение, полученное в результате хэширования/сжатия, и другое целочисленное значение, используемое для адресации в рамках массива arData.



Тут есть один нюанс: память таблицы преобразования предусмотрительно располагается за вектором arData. Это не даёт таблице использовать иную область памяти, ведь она уже размещена рядом с arData и, значит, остаётся в том же адресном пространстве. Это позволяет улучшить локальность данных. Вот так выглядит описанная схема в случае с 8-элементной хэш-таблицей (минимально возможный размер):







Теперь наш ключ foo хэширован в DJB33X и сжат по модулю до необходимого размера (nTableMask). Полученное значение — это индекс, который можно использовать, чтобы обращаться к ячейкам преобразования arData (а не к прямым ячейкам!).



Доступ к этим ячейкам происходит с помощью отрицательного сдвига от начальной позиции arData. Две области памяти были объединены, поэтому мы можем хранить данные в памяти последовательно. nTableMask соответствует отрицательному значению размера таблицы, так что, взяв его в качестве модуля, получим значение от 0 до –7. Теперь можно обращаться к памяти. При размещении в ней всего буфера arData мы рассчитываем его размер по формуле:



Размер таблицы * размер bucket’а + размер таблицы * размер(uint32) ячеек преобразования.



Ниже хорошо видно, как буфер делится на две части:



#define HT_HASH_SIZE(nTableMask) (((size_t)(uint32_t)-(int32_t)(nTableMask)) * sizeof(uint32_t))
#define HT_DATA_SIZE(nTableSize) ((size_t)(nTableSize) * sizeof(Bucket))
#define HT_SIZE_EX(nTableSize, nTableMask) (HT_DATA_SIZE((nTableSize)) + HT_HASH_SIZE((nTableMask)))
#define HT_SIZE(ht) HT_SIZE_EX((ht)->nTableSize, (ht)->nTableMask)

Bucket *arData;
arData = emalloc(HT_SIZE(ht)); /* теперь разместим это в памяти */


Когда макросы выполнены, мы получаем:



(((size_t)(((ht)->nTableSize)) * sizeof(Bucket)) + (((size_t)(uint32_t)-(int32_t)(((ht)->nTableMask))) * sizeof(uint32_t)))


Замечательно.



Разрешение коллизий



Теперь разберёмся, как разрешаются коллизии. Как вы помните, в хэш-таблице несколько ключей при хэшировании и сжатии могут соответствовать одному и тому же индексу преобразования. Так что, получив индекс преобразования, мы с его помощью извлекаем данные обратно из arData и сравниваем хэши и ключи, проверяя, то ли это, что нужно. Если данные неправильные, мы проходим по связному списку с помощью поля zval.u2.next, в котором отражается следующая ячейка для внесения данных.



Обратите внимание, что связный список не рассеян по памяти, как традиционные связные списки. Вместо того чтобы ходить по нескольким размещённым в памяти указателям, полученным от кучи — и наверняка разбросанным по адресному пространству, — мы считываем из памяти полный вектор arData. И это одна из главных причин увеличения производительности хэш-таблиц в РНР 7, а также всего языка.



В РНР 7 у хэш-таблиц очень высокая локальность данных. В большинстве случаев доступ происходит за 1 наносекунду, поскольку данные обычно находятся в процессорном кэше первого уровня.



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



idx = ht->nNumUsed++; /* берём номер следующей доступной ячейки */
ht->nNumOfElements++; /* инкрементируем количество элементов */
/* ... */
p = ht->arData + idx; /* получаем от arData bucket в этой ячейке */
p->key = key; /* помечаем ключ для вставки */
/* ... */
p->h = h = ZSTR_H(key); /* сохраняем в bucket хэш текущего ключа */
ZVAL_COPY_VALUE(&p->val, pData); /* копируем значение в значение bucket’а: операция добавления */

nIndex = h | ht->nTableMask; /* получаем индекс таблицы преобразования */
Z_NEXT(p->val) = HT_HASH(ht, nIndex); /* помести следующего из нас как фактический элемент */
HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx); /* помести нас в фактическую ячейку преобразования */


То же самое и с удалением:



h = zend_string_hash_val(key); /* получаем хэш из строкового ключа */
nIndex = h | ht->nTableMask; /* получаем индекс таблицы преобразования */

idx = HT_HASH(ht, nIndex); /* получаем ячейку, соответствующую индексу преобразования */
while (idx != HT_INVALID_IDX) { /* если есть соответствующая ячейка */
p = HT_HASH_TO_BUCKET(ht, idx); /* получаем bucket из этой ячейки */
if ((p->key == key) || /* это правильный? Тот же указатель ключа? */
(p->h == h && /* ...или тот же хэш */
p->key && /* и ключ (на базе строкового ключа) */
ZSTR_LEN(p->key) == ZSTR_LEN(key) && /* и та же длина ключа */
memcmp(ZSTR_VAL(p->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0)) { /* и то же содержимое ключа? */
_zend_hash_del_el_ex(ht, idx, p, prev); /* вот они мы! Удаляй нас */
return SUCCESS;
}
prev = p;
idx = Z_NEXT(p->val); /* переместиться к следующей ячейке */
}
return FAILURE;


Ячейки преобразования и инициализация хэша



HT_INVALID_IDX — специальный флаг, который мы помещаем в таблицу преобразования. Он означает «это преобразование никуда не ведёт, продолжать не нужно».



Двухэтапная инициализация даёт определённые преимущества, позволяя максимально уменьшить влияние пустой, только что созданной хэш-таблицы (частый случай в РНР). При создании таблицы мы одновременно создаём ячейки bucket’ов в arData и две ячейки преобразования, в которые помещаем флаг HT_INVALID_IDX. Затем накладываем маску, которая направляет в первую ячейку преобразования (HT_INVALID_IDX, здесь нет данных).



#define HT_MIN_MASK ((uint32_t) -2)
#define HT_HASH_SIZE(nTableMask) (((size_t)(uint32_t)-(int32_t)(nTableMask)) * sizeof(uint32_t))
#define HT_SET_DATA_ADDR(ht, ptr) do { (ht)->arData = (Bucket*)(((char*)(ptr)) + HT_HASH_SIZE((ht)->nTableMask)); } while (0)

static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX};

/* hash lazy init */
ZEND_API void ZEND_FASTCALL _zend_hash_init(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
{
/* ... */
ht->nTableSize = zend_hash_check_size(nSize);
ht->nTableMask = HT_MIN_MASK;
HT_SET_DATA_ADDR(ht, &uninitialized_bucket);
ht->nNumUsed = 0;
ht->nNumOfElements = 0;
}


Обратите внимание, что здесь можно не пользоваться кучей. Вполне достаточно статической постоянной области памяти (static const memory zone), так получается гораздо легче (uninitialized_bucket).



После добавления первого элемента мы полностью инициализируем хэш-таблицу. Иными словами, создаём последние необходимые ячейки преобразования в зависимости от запрошенного размера (по умолчанию начинается с 8 ячеек). Размещение в памяти происходит из кучи.



(ht)->nTableMask = -(ht)->nTableSize;
HT_SET_DATA_ADDR(ht, pemalloc(HT_SIZE(ht), (ht)->u.flags & HASH_FLAG_PERSISTENT));
memset(&HT_HASH(ht, (ht)->nTableMask), HT_INVALID_IDX, HT_HASH_SIZE((ht)->nTableMask))


Макрос HT_HASH позволяет обращаться к ячейкам преобразования в той части размещённого в памяти буфера, для которой использовано отрицательное смещение. Табличная маска всегда отрицательная, потому что ячейки таблицы преобразования индексируются в минус от начала буфера arData. Здесь во всей красе раскрывается программирование на С: вам доступны миллиарды ячеек, плавайте в этой бесконечности, только не утоните.



Пример лениво инициализированной (lazy-initialized) хэш-таблицы: она создана, но пока ни один хэш в неё не помещён.







Фрагментация хэшей, увеличение и уплотнение



Если хэш-таблица заполнена целиком и нужно добавить новые элементы, приходится увеличивать её размер. Это большое преимущество хэш-таблиц по сравнению с классическими жёстко ограниченными массивами в С. При каждом увеличении размер хэш-таблицы вырастает вдвое. Напоминаю, что при увеличении размера таблицы мы предварительно распределяем в памяти С-массив arBucket, а в пустые ячейки помещаем специальные значения UNDEF. В результате мы бодро теряем память. Потери вычисляются по формуле:



(новый размер – старый размер) * размер Bucket



Вся эта память состоит из UNDEF-ячеек и ждёт, когда в неё поместят данные.



К примеру, у вас в хэш-таблице 1024 ячейки, и вы добавляете новый элемент. Таблица разрастается до 2048 ячеек, из которых 1023 — пустые. 1023 * 32 байта = примерно 32 Кб. Это один из недостатков реализации хэш-таблиц в РНР.



Нужно помнить, что хэш-таблица может целиком состоять из одних UNDEF-ячеек. Если добавлять и удалять по много элементов, то таблица будет фрагментироваться. Но ради сохранения порядка следования элементов мы всё новое добавляем только в конце arData, а не вставляем в образовавшиеся пробелы. Поэтому может возникнуть ситуация, когда мы доходим до конца arData, хотя в нём ещё присутствуют UNDEF-ячейки.



Пример сильно фрагментированной 8-ячеечной хэш-таблицы:







Как вы помните, в UNDEF-ячейках нельзя хранить новые значения. В вышеприведённой схеме при итерировании хэш-таблицы мы идём от arData[0] к arData[7].



Увеличив размер, можно сократить вектор arData и в конце концов заполнить пустые ячейки, просто перераспределив данные. Когда таблице дают команду на изменение размера, то в первую очередь она пытается себя уплотнить. Затем вычисляет, придётся ли ей после уплотнения снова увеличиваться. И если оказывается, что да, то таблица увеличивается в два раза. После этого вектор arData начинает занимать вдвое больше памяти (realloc()). Если же увеличиваться не нужно, то данные просто перераспределяются в уже размещённых в памяти ячейках. Здесь используется алгоритм, который мы не можем применять при каждом удалении элементов, поскольку он слишком часто тратит ресурсы процессора, а выхлоп не так велик. Вы же помните о знаменитом программистском компромиссе между процессором и памятью?



На этой иллюстрации представлена предыдущая фрагментированная хэш-таблица после уплотнения:







Алгоритм просматривает arData и заполняет каждую UNDEF-ячейку данными из следующей неUNDEF-ячейки. Упрощённо это выглядит так:



Bucket *p;
uint32_t nIndex, i;
HT_HASH_RESET(ht);
i = 0;
p = ht->arData;

do {
if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) {
uint32_t j = i;
Bucket *q = p;
while (++i < ht->nNumUsed) {
p++;
if (EXPECTED(Z_TYPE_INFO(p->val) != IS_UNDEF)) {
ZVAL_COPY_VALUE(&q->val, &p->val);
q->h = p->h;
nIndex = q->h | ht->nTableMask;
q->key = p->key;
Z_NEXT(q->val) = HT_HASH(ht, nIndex);
HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(j);
if (UNEXPECTED(ht->nInternalPointer == i)) {
ht->nInternalPointer = j;
}
q++;
j++;
}
}
ht->nNumUsed = j;
break;
}
nIndex = p->h | ht->nTableMask;
Z_NEXT(p->val) = HT_HASH(ht, nIndex);
HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(i);
p++;
} while (++i < ht->nNumUsed);


API хэш-таблицы



Ну что же, теперь мы знаем основные моменты реализации хэш-таблицы в PHP 7. Давайте теперь рассмотрим её публичный API.



Тут особо говорить не о чем (ну, в РНР 5 API сделан куда лучше). Просто при использовании функции API не забывайте о трёх вещах:




  • О вашей операции (добавление, удаление, очистка, уничтожение и т. д.).

  • О типе вашего ключа (целочисленный или строчный).

  • О типе данных, которые вы хотите хранить.





Каким бы ни был ваш ключ, строчным или целочисленным, главное: API должен знать о том, что строчные ключи получают хэши от zend_string, а целочисленные сразу используются в качестве хэша. Поэтому вы можете встретить zend_hash_add(ht, zend_string, data) или zend_hash_index_add(ht, long, data).



Иногда ключ может представлять собой простую пару (char* / int). В этом случае нужно использовать другой API, например zend_hash_str_add(ht, char *, int, data). Помните, что, как бы там ни было, хэш-таблица обратится к zend_string, превратив в него ваш строковый ключ и вычислив его хэш, потратив какое-то количество ресурсов процессора. Если можете использовать zend_string, используйте. Наверняка они уже вычислили свои хэши, поэтому API просто возьмёт их. Например, компилятор РНР вычисляет хэш каждой части используемого строкового, как zend_string. OPCache хранит подобный хэш в памяти общего доступа. Как автор расширений, рекомендую инициализировать в MINIT все ваши литералы zend_string.



Теперь о данных, которые вы собираетесь хранить в хэш-таблице. Опять же, это может быть что угодно, хэш-таблица всё равно поместит данные в zval, имеющийся в каждом Bucket. В РНР 7 zval’ы могут хранить любые данные любого типа. В целом API хэш-таблицы ожидает, что вы упакуете данные в zval, который API воспринимает как значение.



Ситуацию можно несколько упростить, если у вас есть указатель на хранилище или область памяти (данные, на которые ссылается указатель). Тогда API поместит в zval этот указатель или область памяти, а потом сам zval использует указатель в качестве данных.



Примеры помогут понять идею:



zend_hash_str_add_mem(hashtable *, char *, size_t, void *)
zend_hash_index_del(hashtable *, zend_ulong)
zend_hash_update_ptr(hashtable *, zend_string *, void *)
zend_hash_index_add_empty_element(hashtable *, zend_ulong)


При извлечении данных вы получите либо zval *, либо NULL. Если в качестве значения используется указатель, API может вернуть как есть:



zend_hash_index_find(hashtable *, zend_string *) : zval *
zend_hash_find_ptr(hashtable *, zend_string *) : void *
zend_hash_index_find(hashtable *, zend_ulong) : zval *


Что касается _new API наподобие zend_hash_add_new(), его лучше не использовать. Его применяет движок для внутренних нужд. Этот API заставляет хэш-таблицу хранить данные, даже если они уже доступны в хэше (тот же ключ). В результате у вас появятся дубли, что может не лучшим образом сказаться на работе. Так что использовать этот API можно, только если вы полностью уверены, что в хэше нет данных, которые вы собираетесь добавить. Тем самым вы избежите необходимости искать их.



Напоследок: как и в РНР 5, API zend_symtable_api() заботится о преобразовании текстового написания чисел непосредственно в числовое:



static zend_always_inline zval *zend_symtable_update(HashTable *ht, zend_string *key, zval *pData)
{
zend_ulong idx;

if (ZEND_HANDLE_NUMERIC(key, idx)) { /* handle numeric key */
return zend_hash_index_update(ht, idx, pData);
} else {
return zend_hash_update(ht, key, pData);
}
}


Вы можете использовать множество макросов для итераций в зависимости от того, какие данные хотите получить в цикле: ключ, zval… Все эти макросы основаны на ZEND_HASH_FOREACH:



#define ZEND_HASH_FOREACH(_ht, indirect) do { \
Bucket *_p = (_ht)->arData; \
Bucket *_end = _p + (_ht)->nNumUsed; \
for (; _p != _end; _p++) { \
zval *_z = &_p->val; \
if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \
_z = Z_INDIRECT_P(_z); \
} \
if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue;

#define ZEND_HASH_FOREACH_END() \
} \
} while (0)


Оптимизация «упакованная хэш-таблица»



Итак, вспомните о критически важном правиле: мы добавляем данные в arData в порядке возрастания, от нуля и до конца, а затем формируем вектор arData. Это позволяет легко и дёшево итерировать хэш-таблицу: достаточно просто итерировать С-массив arData. Поскольку в качестве ключа хэш-таблицы может быть использована строка или взятое не по порядку целочисленное значение, то нам нужна таблица преобразования для возможности проанализировать хэш.



Но в одном случае такие таблицы бесполезны: если пользователь применяет только целочисленные ключи и только в порядке возрастания. Тогда при просмотре arData от начала до конца мы получим данные в том же порядке, в котором они были помещены. А раз в этом случае таблица преобразования бесполезна, то можно и не размещать её в памяти.



Эта оптимизация называется «упакованная хэш-таблица» (packed hashtable):







Как видите, все ключи целочисленные и размещены просто в порядке возрастания. При итерировании arData[0] с самого начала мы будем получать элементы в правильной очерёдности. Так что таблица преобразования была уменьшена всего до двух ячеек, 2 * uint32 = 8 байт. Больше ячеек преобразования не нужно. Звучит странно, но с этими двумя ячейками производительность выше, чем совсем без них.



Должен вас предупредить: если вы нарушите правило, скажем, вставив строчный ключ (после хэширования/сжатия), то упакованную хэш-таблицу обязательно придётся преобразовать в классическую. С созданием всех необходимых ячеек преобразования и реорганизацией bucket’ов.



ZEND_API void ZEND_FASTCALL zend_hash_packed_to_hash(HashTable *ht)
{
void *new_data, *old_data = HT_GET_DATA_ADDR(ht);
Bucket *old_buckets = ht->arData;

ht->u.flags &= ~HASH_FLAG_PACKED;
new_data = pemalloc(HT_SIZE_EX(ht->nTableSize, -ht->nTableSize), (ht)->u.flags & HASH_FLAG_PERSISTENT);
ht->nTableMask = -ht->nTableSize;
HT_SET_DATA_ADDR(ht, new_data);
memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed);
pefree(old_data, (ht)->u.flags & HASH_FLAG_PERSISTENT);
zend_hash_rehash(ht);
}


Хэш-таблица u.flags используется для того, чтобы определить, упакованная ли основная хэш-таблица. Если да, то её поведение будет отличаться от поведения традиционной хэш-таблицы, ей не понадобятся ячейки преобразования. В исходном коде вы можете найти немало мест, когда упакованные и классические хэши идут по разным ветвям кода. Например:



static zend_always_inline zval *_zend_hash_index_add_or_update_i(HashTable *ht, zend_ulong h, zval *pData, uint32_t flag ZEND_FILE_LINE_DC)
{
uint32_t nIndex;
uint32_t idx;
Bucket *p;

/* ... */
if (UNEXPECTED(!(ht->u.flags & HASH_FLAG_INITIALIZED))) {
CHECK_INIT(ht, h < ht->nTableSize);
if (h < ht->nTableSize) {
p = ht->arData + h;
goto add_to_packed;
}
goto add_to_hash;
} else if (ht->u.flags & HASH_FLAG_PACKED) {
/* ... */
} else if (EXPECTED(h < ht->nTableSize)) {
p = ht->arData + h;
} else if ((h >> 1) < ht->nTableSize &&
(ht->nTableSize >> 1) < ht->nNumOfElements) {
zend_hash_packed_grow(ht);
p = ht->arData + h;
} else {
goto convert_to_hash;
}
/* ... */


Подытожим: упакованные хэш-таблицы позволяют оптимизировать использование и памяти, и процессора.




  • Память: разница в потреблении байт по сравнению с классической: (размер_таблицы - 2) * размер(uint32). Если у вас тысячи ячеек, то счёт идёт на килобайты.

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



Тем не менее если вы начнёте использовать строчные ключи или нарушите порядок сортировки целочисленных ключей (скажем, вставите 42 после 60), то хэш-таблицу придётся конвертировать в «классическую». На это вы потратите немного ресурсов процессора (на крупных массивах — несколько больше) и дополнительный объём памяти. Для создания упакованной хэш-таблицы достаточно сказать API:



void ZEND_FASTCALL zend_hash_real_init(HashTable *ht, zend_bool packed)


Имейте в виду, что zend_hash_real_init() — полный этап инициализации, а не «ленивый» (zend_hash_init()). Обычно, если инициализация «ленивая», то хэш-таблица изначально упакованная. А когда складываются соответствующие обстоятельства, она преобразуется в классический хэш.



Массивы в РНР



Давайте посмотрим, как можно проверить детали реализации хэш-таблиц в рамках пользовательского кода.



Память хэш-таблиц и оптимизация упакованных массивов



Сначала продемонстрируем оптимизацию упакованных массивов (packed array):



function m()
{
printf("%d\n", memory_get_usage());
}

$a = range(1,20000); /* range() создаёт упакованный массив */

m();

for($i=0; $i<5000; $i++) {
/* Продолжаем добавлять ключи в порядке возрастания,
* поэтому контракт упакованного массива всё ещё действителен:
* мы остаёмся в «упакованном» режиме */
$a[] = $i;
}

m();

/* Мы неожиданно нарушили контракт упакованного массива и заставили
* хэш-таблицу превратиться в «классическую», потратив больше
* памяти на ячейки преобразования */
$a['foo'] = 'bar';

m();


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



1406744
1406776
1533752


При переходе от упакованного массива к классическому хэшу потребление памяти выросло примерно на 130 Кб (для массива с 25 000 элементов).



Теперь продемонстрируем действие алгоритма уплотнения или увеличения:



function m()
{
printf("%d\n", memory_get_usage());
}

/* Размер хэш-таблицы соответствует двойке в степени. Давайте создадим
* массив из 32 768 ячеек (2^15). Используем упакованный массив */
for ($i=0; $i<32768; $i++) {
$a[$i] = $i;
}

m();

/* Теперь очистим его */
for ($i=0; $i<32768; $i++) {
unset($a[$i]);
}
m();

/* Добавим один элемент. Размер хэш-таблицы превысит предел,
* что запустит работу алгоритма уплотнения или увеличения */
$a[] = 42;

m();


Результаты:



1406864
1406896
1533872


Когда таблица заполнена и мы её опустошаем, потребляемая память не изменяется (шум по модулю, modulo noise). После применения unset() к каждому значению наша таблица будет иметь arData на 32 768 ячеек, заполненных UNDEF-zval’ами.



Теперь добавим что-нибудь в качестве следующего элемента. Помните nNumUsed, который используется для адресации arData и увеличивающийся при каждом добавлении? Теперь он переполнит размер таблицы, и нужно будет решать, уплотнять её или увеличивать размер.



Возможно ли уплотнение?



Ответ кажется очевидным — да, ведь у нас тут сплошные UNDEF-ячейки. Но на самом деле нет: нам нужно сохранять очерёдность данных, потому что мы используем упакованный массив и не хотели бы, чтобы он превратился в классический, пока нам совсем не припрёт. Если мы добавим элемент в уже существующую ячейку, то нарушим очерёдность, что запустит процедуру уплотнения, а не увеличения размера.



/* Код тот же, что и выше, но: */

/* здесь мы добавляем ещё один элемент к известному индексу (idx).
* Размер хэша переполнится, запустится алгоритм уплотнения или увеличения */
$a[3] = 42;

m();


Использование памяти:



1406864
1406896
1406896


Видите разницу? Теперь алгоритм не увеличил нашу таблицу с 32 768 до 65 538 ячеек, а уплотнил её. У таблицы в памяти всё ещё размещено 32 767 ячеек. А поскольку ячейка представляет собой Bucket, внутри которого находится zval, чей размер равен long (в нашем случае 42), то память не изменяется. Ведь в zval уже входит размер long. :) Следовательно, теперь мы можем повторно использовать эти 32 768 ячеек, поместив в них целочисленные значения, булевы, с плавающей запятой. А если в качестве значений мы используем строки, объекты, другие массивы и т. д., то выделится дополнительная память, указатель на которую будет храниться в предварительно размещённых в памяти UNDEF-zval’ах нашего «горячего» массива.



То же самое мы можем проделать с классической хэш-таблицей, просто используя строчные ключи. Когда мы переполним её на один элемент, таблица уплотнится, а не увеличится, потому что здесь не нужно сохранять очерёдность. Мы в любом случае находимся в «неупакованном» режиме, просто добавляем ещё одно значение на крайнюю левую позицию (idx 0), а все последующие — это UNDEF-zval.



function m()
{
printf("%d\n", memory_get_usage());
}

/* Размер хэш-таблицы соответствует двойке в степени. Давайте создадим
* массив из 32 768 ячеек (2^15). Здесь мы НЕ используем упакованный массив */
for ($i=0; $i<32768; $i++) {
/* возьмём строковый ключ, чтобы у нас был классический хэш */
$a[' ' . $i] = $i;
}

m();

/* теперь очистим его */
for ($i=0; $i<32768; $i++) {
unset($a[' ' . $i]);
}
m();

/* здесь мы добавляем ещё один элемент к известному индексу.
* Размер хэша переполнится, запустится алгоритм уплотнения или увеличения */
$a[] = 42;

m();


Результаты по памяти:



2582480
1533936
1533936


Как и ожидалось. Массив потребляет около 2,5 Мб. После применения unset() ко всем значениям потребление памяти снижается, мы освобождаем ключи. У нас 32 768 ключей zend_string, и после их освобождения массив начинает потреблять 1,5 Мб.



Если теперь добавить один элемент, то произойдёт переполнение внутреннего размера таблицы, что запустит алгоритм уплотнения или увеличения. Поскольку массив не упакованный, не нужно сохранять очерёдность элементов, и таблица будет уплотнена. Новое значение 42 записывается в idx 0, объём памяти не меняется. Конец истории.



Как видите, в некоторых редких случаях упакованные хэш-таблицы могут нам даже навредить, увеличившись в размере вместо уплотнения. Но разве в реальных проектах вы станете использовать такой глупый код? Это не должно влиять на повседневное программирование, но если вам нужна действительно высокая производительность (фреймворки, вы меня слышите?) и/или вы хотите оптимизировать высоконагруженные пакетные скрипты, то это может быть хорошим решением. Вместо того чтобы ломать голову над переходом к чистому С при решении подобных задач. Если у вас нет многих тысяч элементов, то потребление памяти будет смехотворным. Но в наших примерах было «всего» от 20 до 32 тысяч элементов, а разница измерялась в килобайтах и мегабайтах.



Неизменяемые массивы (Immutable arrays)



Неизменяемые массивы — это характерная особенность OPCache. Не включив OPCache, вы не сможете воспользоваться преимуществами неизменяемых массивов, т. е. доступных только для чтения. Если же OPCache активирован, то он просматривает содержимое каждого скрипта и пытается скопировать многие вещи в совместно используемую память. В частности, константные AST-ноды. Собственно, константный массив — это константная AST-нода. Например:



$a = ['foo', 1, 'bar'];


Здесь $a — константная AST-нода. Компилятор определяет, что это заполненный константами массив, и превращает его в константную ноду. Далее OPCache просматривает все скрипты в AST-ноде, копирует их связанный с процессом адрес (адрес кучи) в сегмент совместной используемой памяти, а затем этот адрес освобождает. Дополнительную информацию об OPCache вы можете найти в обзоре этого расширения.



Кроме того, OPCache превращает массивы в неизменяемые, устанавливая им флаги IS_ARRAY_IMMUTABLE и IS_TYPE_IMMUTABLE. Когда движок встречает IS_IMMUTABLE, он обращается с этими данными особым образом. Если вы преобразуете неизменяемый массив в переменную, то он не будет дуплицирован. В противном случае создаётся полная копия массива.



Пример описанной оптимизации:



$ar = [];
for ($i = 0; $i < 1000000; ++$i) {
$ar[] = [1, 2, 3, 4, 5, 6, 7, 8];
}


Этот скрипт потребляет около 400 Мб памяти без OPCache, а с ним — примерно 35 Мб. Если OPCache отключён, то в скрипте движок создаёт полную копию 8-элементного массива в каждом слоте $ar. В результате в памяти размещается один миллион 8-слотовых массивов. А если включить OPCache, то он пометит 8-слотовый массив как IS_IMMUTABLE и при запуске скрипта в слоты $ar будет копироваться всего лишь указатель массива, что предотвращает полное дуплицирование в каждом цикле.



Очевидно, что если позднее вы измените один из этих массивов, скажем, $ar[42][3] = 'foo';, то 8-слотовый массив в $ar[42] будет полностью дуплицирован механизмом копирования при записи.



Во внутренних хэш-таблицах была сделана ещё одна, практически случайная оптимизация. Как вы помните, PHP-массивы — всего лишь частный случай хэш-таблиц Zend. Поскольку вы в силах манипулировать ими посредством PHP, то можете представить и способ применения. Но хэш-таблицы используются по всему движку. Например, любой скрипт может содержать функции и/или классы. Эти две большие схемы хранятся в двух хэш-таблицах. Компилятор преобразует скрипт в так называемый OPArray и прикрепляет к нему таблицу функции (она может быть пустой) и таблицу класса (тоже может быть пустой). Когда PHP завершает текущий запрос, он очищает этот OPArray: уничтожает таблицы функции и класса. Но при включённом OPCache оба этих массива также становятся IMMUTABLE, и в результате движок не может их уничтожить. Как только появится следующий запрос, они сразу будут загружены из совместно используемой памяти и запросят точно такой же скрипт.



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



Но не дайте неизменяемым массивам вас запутать. К примеру, на данный момент подобный сценарий не имеет никакой оптимизации:



$a = [1];
$b = [1];


Здесь в памяти создаётся два массива. И нет процесса наподобие того, что используется для изолированных строк (interned strings), когда движок отслеживает каждую часть использованной строки, чтобы при необходимости использовать её снова. Неизменяемые массивы — это массивы, заполненные неизменяемыми типами (нет переменных, нет вызовов функций, всё решается во время компиляции), которые не дуплицируются в памяти при переносе из одного места в другое в ходе runtime’а PHP. Также эти типы никогда не уничтожаются в памяти (от одного запроса к другому). Кроме того, неизменяемые массивы используются только при активированном расширении OPCache.
Original source: habrahabr.ru.

https://habrahabr.ru/post/308240/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Жонглируем версиями PHP в системе

Понедельник, 22 Августа 2016 г. 10:41 (ссылка)

Проблема “ хочу новую версию %software% на моем стареньк… стабильном Debian/CentOS…” так же стара, как *nix-мир. Способов добиться желаемого хватает. Есть масса решений как притащить в систему несколько версий одного и того же софта. Но дальше хочется не просто иметь ещё одну версию, но и управлять тем, какая из версий доступна в системе по умолчанию, для конкретных приложений или пользователей.



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





В качестве примера возьмём сервер на CentOS 7, где установлен родной PHP:



# php -v
PHP 5.4.16 (cli) (built: May 12 2016 13:45:17)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies
with the ionCube PHP Loader (enabled) + Intrusion Protection from ioncube24.com (unconfigured) v5.0.12, Copyright (c) 2002-2015, by ionCube Ltd.


Также на сервере установлен наш Plesk с парой своих сборок PHP:



# rpm -qa | grep plesk-php.*-release
plesk-php56-release-5.6.22-centos7.16052711.x86_64
plesk-php70-release-7.0.7-centos7.16052710.x86_64


Допустим, мы хотим переключить систему на использование PHP 5.6 по умолчанию (переключать глобально PHP с версии 5.4 на 7 как-то сс… страшно — чему-то в системе может поплохеть от такого). Бинарь PHP 5.6 лежит у нас тут:



# /opt/plesk/php/5.6/bin/php -v
PHP 5.6.22 (cli) (built: May 27 2016 11:45:28)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
with the ionCube PHP Loader (enabled) + Intrusion Protection from ioncube24.com (unconfigured) v5.0.18, Copyright (c) 2002-2015, by ionCube Ltd.
with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies


Как же сделать так, чтобы система использовала эту, нужную нам, версию PHP?



Сначала посмотрим на системную переменную PATH



# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin


В ней перечислен список директорий, в которых ищутся программы по имени. Главный нюанс — поиск в директориях происходит последовательно и используется первый найденный результат. Текущий путь до текущего бинарника PHP мы можем увидеть с помощью команды:



# which php
/usr/bin/php


Как видно из PATH, /usr/local/bin находится в списке раньше, чем /usr/bin. Значит, если мы поместим ссылку на альтернативную версию PHP “пораньше”, в /usr/local/bin, то именно она и будет использоваться при вызове команды php вместо /usr/bin/php. Мы можем создать эту ссылку руками (и всё даже будет работать), но правильнее использовать специально созданную для этих целей утилиту update-alternatives (в CentOS это просто alternatives, но есть симлинка update-alternatives, поэтому дальше будем оперировать именно этой командой, как универсальной для Debian/Ubuntu/CentOS/и т.д.).



Теперь, давайте зарегистрируем все доступные версии PHP с помощью этой команды:



# update-alternatives --install /usr/local/bin/php php /opt/plesk/php/5.6/bin/php 10
# update-alternatives --install /usr/local/bin/php php /opt/plesk/php/7.0/bin/php 20
# update-alternatives --install /usr/local/bin/php php /usr/bin/php 30


Цифры 10, 20 и 30 — это приоритет. Он работает для автоматического выбора, если администратор сам не выбрал конкретную версию. Самое большое число определяет выбор "по умолчанию".



Проверим, что php теперь указывает на созданную командой симлинку:



# update-alternatives --list | grep php
php auto /usr/bin/php

# update-alternatives --display php
php - status is auto.
link currently points to /usr/bin/php
/opt/plesk/php/5.6/bin/php - priority 10
/opt/plesk/php/7.0/bin/php - priority 20
/usr/bin/php - priority 30
Current `best' version is /usr/bin/php.


Давайте разберемся, что же update-alternatives сделала для нас:



# which php
/usr/local/bin/php
# ls -l /usr/local/bin/php
lrwxrwxrwx. 1 root root 21 Jul 2 10:03 /usr/local/bin/php -> /etc/alternatives/php
# ls -l /etc/alternatives/php
lrwxrwxrwx. 1 root root 26 Jul 2 10:03 /etc/alternatives/php -> /usr/bin/php


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



# php -v
PHP 5.4.16 (cli) (built: May 12 2016 13:45:17)
...


То есть, мы успешно настроили группу PHP в update-alternatives, где по умолчанию в автоматическом режиме выбран системный PHP. Сейчас у нас есть возможность переключить команду PHP на любую другую версию..



Давайте переключимся на PHP версии 5.6, которая идет в поставке с Plesk'ом:



# update-alternatives --config php

There are 3 programs which provide 'php'.

Selection Command
-----------------------------------------------
1 /opt/plesk/php/5.6/bin/php
2 /opt/plesk/php/7.0/bin/php
*+ 3 /usr/bin/php

Enter to keep the current selection[+], or type selection number: 1


Проверяем, что переключение произошло:



# php -v
PHP 5.6.22 (cli) (built: May 27 2016 11:45:28)

# update-alternatives --display php
php - status is manual.
link currently points to /opt/plesk/php/5.6/bin/php


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



С помощью update-alternatives можно выбирать не только версию PHP, но и многие другие вещи, например разные версии phpunit или редактор по умолчанию в системе. Подход этот универсален для различных систем. Не изобретая своего велосипеда, используя существующие инструменты, вы можете быть уверенным, что не устроили для ваших коллег квеста “Ну почему оно так работает?!”. Настраивайте свою систему правильно.



P.S. Приглашаю пофлеймить про phpenv в комментарии :)


Original source: habrahabr.ru.

https://habrahabr.ru/post/306682/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Не используйте Illuminate Support

Воскресенье, 21 Августа 2016 г. 09:11 (ссылка)

tl;dr: Если Вы пишете framework agnostic пакет, не используйте illuminate/support.



laravel





Множество framework agnostic Composer пакетов (PHP) зависят от illuminate/support, который включает в себя хелперы и код общего назначения, используемый в Laravel framework. А всё потому, что данный пакет содержит в себе множество замечательных функций типа array_get, а также великолепные коллекции.



Хелперы — отличная штука, но я не думаю, что разработчики понимают все последствия включения данного пакета в свой проект. Все боятся критики за изобретение велосипеда, поэтому тянут 6000+ строк кода чтобы самим не писать такой код



isset($arr[$k]) ? $arr[$k] : null


Ад зависимостей



Используя illuminate/support (5.2) Вы подтягиваете в свой проект illuminate/contracts, doctrine/inflector, полифилл для random_bytes, и mb_string. К счастью дерево зависимостей на этом заканчивается.



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



Конфликт версий



Более 6000 пакетов зависят от illuminate/support. Если кто-нибудь установит Ваш пакет и другой, включающий в себя illuminate/support, он должен быть зависим от той же версии, иначе получится конфликт. Беглый взгляд показывает, что множество проектов всё ещё используют версию 4.1.x.



Положение ухудшается, если Ваш пакет используется вместе с Laravel или Lumen. Пользователь не сможет обновиться раньше Вас (и все пакеты, использующие illuminate/support). Теперь Вы мешаете пользователю обновить свой фреймворк. Единственная альтернатива, использовать неограниченные диапазоны вроде >5.2, но это довольно плохая идея.



Глобальная область видимости



illuminate/support подтягивает 52 функции в глобальную область видимости. Хорошо если используемый фреймворк не использует пространство имён. Зависимости не должны загрязнять глобальную область.



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



Фасады



Это конечно небольшая проблема, но сильно раздражает когда пишешь код по 40+ часов в неделю. illuminate/support имеет множество часто используемых классов типа Collection, Request, Response, и App. Каждый раз, когда я начинаю набирать пространство имён, IDE пытается импортировать неправильную коллекцию или бесполезный фасад вместо фактического класса, который мне нужен. illuminate/support включает в себя целый каталог классов которые даже не работают за пределами Laravel! Я гарантирую, что Вы не используете фасады в рамках framework agnostic, поэтому, пожалуйста, прекратите тянуть их в мой проект.



Критические ошибки



Сейчас ваш пакет зависит от 3-х различных пакетов, не нарушающих SemVer. Стоит ли рисковать получить такие же проблемы как с left-pad вместо написания нескольких строк кода?



Нацеливание на меньшее количество зависимостей



Попробуйте использовать как можно меньше зависимостей. Если посмотреть на проекты от phpleague, то можно заметить, что все они имеют малое количество зависимостей или вообще их не имеют. Хорошая практика — написать несколько небольших вспомогательных классов для 2-х или 3-х функций. Если не хочется писать самостоятельно, скопируйте функции которые Вам нужны в соответствии с лицензией.



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



Альтернативы



Если Вам нужна тонна функционала, следует использовать пакеты вместо того, чтобы всё писать самому. Поскольку illuminate/support охватывает огромное количество функционала, ниже список тех, что можно использовать для каждого конкретного случая.



doctrine/inflector



Приведение слов к единственному и множественному числу. Это на самом деле зависимость illuminate/support.



danielstjules/Stringy



Охватывает все функции преобразования строк. Использовался в illuminate/support до версии 5.2.



dusank/knapsack



Работа с коллекциями. Единственные пакет, обнаруженный мной сравнимый с Laravel collections.



anahkiasen/underscore-php



Замена функциям для работы с массивами, использует точечную нотацию. Я не знаю, хорошей альтернативы, поддерживающей точечную без использования зависимостей. Я просто пишу ванильный php для этого. В php7+ можно использовать null coalesce operator вместо array_get.



Оригинал


Original source: habrahabr.ru.

https://habrahabr.ru/post/308166/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Рефакторинг — мощь сокрытая в качественном коде

Понедельник, 15 Августа 2016 г. 13:55 (ссылка)

Рефакторинг — это крайне важная часть процесса разработки. Писать код «который работает» не есть залогом производительного приложения. Рефакторинг дает возможность привести код в надлежащий вид, что позволит в дальнейшем этот код легко читать, использовать повторно, поддерживать и расширять саму систему.



Проектирование



Начало хорошего кода — это всегда проектирование. Программисты, которые не умеют утихомирить страсть к написанию кода, этим опуская проектирование, пишут обычно быстро, но не качественно. Я это знаю, так как сам имел ту же проблему. Проектирование дает возможность взглянуть на систему, которой еще фактически нету, продумать правильную структуру приложения и данных, увидеть тонкости, риски, подумать о производительности и безопасности. При этом проектирование это не только прерогатива начала проекта. Проектирование — это неотъемлемая часть, при разработке любой «Feature».



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



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



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



Несколько принципов, которые точно нужно знать при проектировании классов вашей «Feature»:



1. SOLID (single responsibility, open-closed, Liskov substitution, interface segregation и dependency inversion)



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



2. DRY (do not repeat yourself)



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



К примеру:



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



— Вместо использования функции progress50(), лучше применить более абстрактную progress($percent).



— Отдавать предпочтение внешним зависимостям между модулями, внутренним (DI), что делает модуль более гибким и позволяет его использовать в нескольких местах.



3. KISS (keep it simple, stup...)



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



К примеру:



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



Стиль кода



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



Стандарт стиля кода (и не только) PSR (PHP Standards Recommendations), здесь можно ознакомиться. Содержимое на английском языке, так как английский более ясно дает понять степень применения одного или другого правила («MUST», «MUST NOT», «REQUIRED», «SHALL», «SHALL NOT», «SHOULD», «SHOULD NOT», «RECOMMENDED», «MAY», and «OPTIONAL»).



Несколько замечаний, которые автор счел важными:



1. Ознакомьтесь с PHPDOC для написания комментариев к вашему коду.



2. Лучший комментарий — это правильно названный класс, метод, параметр или переменная.



3. Используйте утилиты PHPMD, PHPCS, их применение шире, чем только для определения несоответствий в стиле кода. Вот документация: PHPMD, PHPCS.



4. Используйте продвинутое IDE.



Рефакторинг в чистом виде



Очень простая аксиома — на продакшн должен попадать только код, прошедший рефакторинг. Иногда после разработки вы сами делаете рефакторинг, что очень даже не плохо (к примеру, разработка через тестирование вообще включает рефакторинг, как обязательный шаг, так как изначально пишется «работающий код», а потом уже «чистый»), но для того, чтобы код был по-настоящему качественным, он должен пройти проверку кода (code-review) другим программистом. Если проект позволяет выделить время на проверку кода, то на таком проекте ты будешь учиться писать код чище и чище, что в последствии приведет к автоматическому написанию качественного кода.



Я помню, как работал на одну компанию, и проверку кода у меня делал человек, который не всегда мог сдерживать свои эмоции, из-за чего о моем не чистом коде слышали все в open space. Поэтому мне приходилось учиться писать качественный код, очень быстро, чтобы избежать подобных случаев.



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



1. Длинные методы (лучше разделить функционал на несколько методов).



2. Громоздкие классы (ваш класс должен исполнять одну функциональную задачу в вашей системе).



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



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



5. Классы, содержащие одинаковые переменные и методы. Проблему можно решить через создание дополнительного класса).



6. Сложно читаемый IF (выражение можно вынести в отдельную переменную и разделить на логические части, которые также вынести в переменные, если много проверок на null, то лучше всего использовать NullObject — количество проверок значительно уменьшится).



7. Громоздкий SWITH (выносим в отдельный метод).



8. Использование наследования из-за одинаковых методов и свойств, в разных по своей сути сущностях (кошка и стул имеют ноги, но их нельзя группировать в категорию «животные»).



9. Слишком большое количество маленьких классов для выполнения одной задачи, которые в последствии сложнее поддерживать.



10. Слишком сложный функционал в одном классе, который можно разделить на несколько классов.



11. Класс делает слишком мало, чтобы его оставлять в системе.



12. «Мертвый код» — его следует удалить.



13. Не использованные структуры классов, которые вы проектировали на будущее, но они так и не пригодились — такие лучше удалить.



14. Методы класса больше используются в другом классе, а в своем вообще не используются или же реже (стоит перенести метод в тот класс, где он больше используется).



15. Слишком длинная цепочка вызовов ($a->b()->c()->d()->e()), в этом случае стоит создать дополнительные методы.



16. Класс, содержащий только один метод, который создает другой класс. (Такой класс нужно использовать с умом, к примеру, для паттерна «Прокси», в противном случае этот класс только увеличивает время и ресурс на поддержку проекта).



17. Слишком много действий в конструкторе. (Конструктор должен только устанавливать свойства класса, если же в конструкторе создаются другие классы, происходят какие-то расчеты, то это делает его сложным для понимания, приходится вникать в суть реализации. Чтобы создать объект и выполнить какие-то действия, добавьте статический метод create($param1, ...), который создаст экземпляр класса с дополнительными действия над ним, этот метод можно назвать более подходящим к тому, что он будет все же делать).



Список литературы



» Source Making

» PSR

» UML
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/307762/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Маршрутизация в CleverStyle Framework

Воскресенье, 14 Августа 2016 г. 15:31 (ссылка)

Многие аспекты CleverStyle Framework имеют альтернативную по отношению к большинству других фреймворков реализацию тех же вещей.

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



Основное отличие



Главное отличие маршрутизации от реализаций в популярных фреймворках типа Symfony, Laravel или Yii это декларативность вместо императивности.



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

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



Основы маршрутизации



Любой URL в представлении фреймворка разбивается на несколько частей.



В самом начале до какой-либо обработки из пути страницы удаляются параметры запроса (? и всё что после него).



Далее мы получаем общий формат пути следующего вида (| используется для разделения выбора из нескольких вариантов, в [] сгруппированы необязательные самостоятельные компоненты пути), пример разбит на несколько строчек для удобства, перед обработкой путь разбивается по слэшах и превращается в массив из частей исходного пути:




[language/]
[admin/|api/|cli/]
[Module_name
[/path
[/sub_path
[/id1
[/another_subpath
[/id2]
]
]
]
]
]


Количество уровней вложенности не ограничено.



Первым делом проверяется префикс языка. Он не участвует в маршрутизации (и может отсутствовать), но при наличии влияет на то, какой язык будет использоваться на странице.

Формат зависит от используемых языков и их количества, может бы простым (en, ru), либо учитывать регион (en_gb, ru_ua).



После языка следует необязательная часть, определяющая тип страницы.

Это может быть страница администрирования ($Request->admin_path === true), запрос к API ($Request->api_path === true), запрос к CLI интерфейсу ($Request->cli_path === true) или обычная пользовательская страница если не указано явно.



Далее определяется модуль, который будет обрабатывать страницу. В последствии этот модуль доступен как $Request->current_module.

Стоит заметить, что название модуля может быть локализовано, к примеру, если для модуля My_blog в переводах есть пара "My_blog" : "Мой блог", то можно в качестве названия модуля использовать Мой_блог, при этом всё равно $Request->current_module === 'My_blog'.



Остаток элементов массива после модуля попадает в $Request->route, который может использоваться модулями, к примеру, для кастомной маршрутизации.



Перед тем, как перейти к следующим этапам, заполняются ещё 2 массива.

$Request->route_ids содержит элементы из $Request->route, которые являются целыми числами (подразумевается что это идентификаторы), $Request->route_path же содержит все элементы $Request->route кроме целых чисел, и используется как маршрут внутри модуля.



Как вклиниться в маршрутизацию на ранних этапах



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



Событие System/Request/routing_replace/before срабатывает сразу перед определением языка страницы и позволяет как-то модифицировать исходный путь в виде строки, самые низкоуровневые манипуляции можно проводит в этом месте.



Событие System/Request/routing_replace/after срабатывает после формирования $Request->route_ids и $Request->route_path, позволяя откорректировать важные параметры после того, как они были определены системой.



Пример добавления поддержки UUID как альтернативы стандартным целочисленным идентификаторам:



Event::instance()->on(
'System/Request/routing_replace/after',
function ($data) {
$route_path = [];
$route_ids = [];
foreach ($data['route'] as $item) {
if (preg_match('/([a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}?)/i', $item)) {
$route_ids[] = $item;
} else {
$route_path[] = $item;
}
}
if ($route_ids) {
$data['route_path'] = $route_path;
$data['route_ids'] = $route_ids;
}
}
);




Структура маршрутов



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



Пример текущей структуры API системного модуля:



{
"admin" : {
"about_server" : [],
"blocks" : [],
"databases" : [],
"groups" : [
"_",
"permissions"
],
"languages" : [],
"mail" : [],
"modules" : [],
"optimization" : [],
"permissions" : [
"_",
"for_item"
],
"security" : [],
"site_info" : [],
"storages" : [],
"system" : [],
"themes" : [],
"upload" : [],
"users" : [
"_",
"general",
"groups",
"permissions"
]
},
"blank" : [],
"languages" : [],
"profile" : [],
"profiles" : [],
"timezones" : []
}


Примеры (реальные) запросов, подходящих под данную структуру:




GET api/System/blank
GET api/System/admin/about_server
SEARCH_OPTIONS api/System/admin/users
SEARCH api/System/admin/users
PATCH api/System/admin/users/42
GET api/System/admin/users/42/groups
PUT api/System/admin/users/42/permissions




Получение окончательного маршрута



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



Для чего это нужно? Допустим, пользователь открывает страницу /Blogs, а структура маршрутов сконфигурирована следующим образом (modules/Blogs/index.json):



[
"latest_posts",
"section",
"post",
"tag",
"new_post",
"edit_post",
"drafts",
"atom.xml"
]


В этом случае $Request->route_path === [], но $App->controller_path === ['index', 'latest_posts'].



index будет здесь вне зависимости от модуля и конфигурации, а вот latest_posts уже зависит от конфигурации. Дело в том, что если страница не API и не CLI запрос, то при указании неполного маршрута фреймворк будет выбирать первый ключ из конфигурации на каждом уровне, пока не дойдет до конца вглубь структуры. То есть Blogs аналогично Blogs/latest_posts.



Для API и CLI запросов в этом смысле есть отличие — опускание частей маршрута подобным образом запрещено и допускается только если в структуре в качестве первого элемента на соответствующем уровне используется _.



К примеру, для API мы можем иметь следующую структуру (modules/Module_name/api/index.json):



{
"_" : []
"comments" : []
}


В этом случае api/Module_name аналогично api/Module_name/_. Это позволяет делать API с красивыми методами (помним, что идентификаторы у нас в отдельном массиве):




GET api/Module_name
GET api/Module_name/42
POST api/Module_name
PUT api/Module_name/42
DELETE api/Module_name/42
GET api/Module_name/42/comments
GET api/Module_name/42/comments/13
POST api/Module_name/42/comments
PUT api/Module_name/42/comments/13
DELETE api/Module_name/42/comments/13




Расположение файлов со структурой маршрутов



Модули в CleverStyle Framework хранят всё своё внутри папки модуля (в противовес фреймворкам, где все view в одной папке, все контроллеры в другой, все модели в третьей, все маршруты в одном файле и так далее) для удобства сопровождения.



В зависимости от типа запроса используются разные конфиги в формате JSON:


  • для обычных страниц modules/Module_name/index.json

  • для страниц администрирования modules/Module_name/admin/index.json

  • для API modules/Module_name/api/index.json

  • для CLI modules/Module_name/cli/index.json



В тех же папках находятся и обработчики маршрутов.



Типы маршрутизации



В CleverStyle Framework есть два типа маршрутизации: основанный на файлах (активно использовался ранее) и основанный на контроллере (более активно используется сейчас).



Возьмем из примера выше страницу Blogs/latest_posts и окончательный маршрут ['index', 'latest_posts'].



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




modules/Blogs/index.php
modules/Blogs/latest_posts.php




Если же используется маршрутизация, основанная на контроллере, то должен существовать класс cs\modules\Blogs\Controller (файл modules/Blogs/Controller.php) со следующими публичными статическими методами:




cs\modules\Blogs\Controller::index($Request, $Response) : mixed
cs\modules\Blogs\Controller::latest_posts($Request, $Response) : mixed




Важно, что любой файл/метод кроме последнего можно опустить, и это не приведет к ошибке.



Теперь возьмем более сложный пример, запрос GET api/Module_name/items/42/comments.



Во-первых, для API и CLI запросов кроме пути так же имеет значение HTTP метод.

Во-вторых, здесь будет использоваться под-папка api.



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




modules/Module_name/api/index.php
modules/Module_name/api/index.get.php
modules/Module_name/api/items.php
modules/Module_name/api/items.get.php
modules/Module_name/api/items/comments.php
modules/Module_name/api/items/comments.get.php




Если же используется маршрутизация, основанная на контроллере, то должен существовать класс cs\modules\Blogs\api\Controller (файл modules/Blogs/api/Controller.php) со следующими публичными статическими методами:




cs\modules\Blogs\api\Controller::index($Request, $Response) : mixed
cs\modules\Blogs\api\Controller::index_get($Request, $Response) : mixed
cs\modules\Blogs\api\Controller::items($Request, $Response) : mixed
cs\modules\Blogs\api\Controller::items_get($Request, $Response) : mixed
cs\modules\Blogs\api\Controller::items_comments($Request, $Response) : mixed
cs\modules\Blogs\api\Controller::items_comments_get($Request, $Response) : mixed




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



Как можно заметить, для API и CLI запросов используется явное разделение кода обработки запросов с разными HTTP методами, в то время как для обычных страниц и страниц администрирования это не учитывается.



Аргументы в контроллерах и возвращаемое значение



$Request и $Response не что иное, как экземпляры cs\Request и cs\Response.



Возвращаемого значения в простых случаях достаточно для задания контента. Под капотом для API запросов возвращаемое значение будет передано в cs\Page::json(), а для остальных запросов в cs\Page::content().



public static function items_comments_get () {
return [];
}
// полностью аналогично
public static function items_comments_get () {
Page::instance->json([]);
}




Несуществующие обработчики HTTP методов



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



API: если нет ни cs\modules\Blogs\api\Controller::items_comments() ни cs\modules\Blogs\api\Controller::items_comments_get() (либо аналогичных файлов), то:


  • в первую очередь будет проверено существования обработчика метода OPTIONS, если он есть — он решает что с этим делать

  • если обработчика метода OPTIONS нет, то автоматически сформированый список существующих методов будет отправлен в заголовке Allow (если вызываемый метод был отличный от OPTIONS, то дополнительно код статуса будет изменен на 501 Not Implemented)



CLI: Аналогично API, но вместо OPTIONS особенным методом является CLI, и вместо заголовка Allow доступные методы будут выведены в консоль (если вызываемый метод был отличный от CLI, то дополнительно статус выхода будет изменен на 245 (501 % 256)).



Использование собственной системы маршрутизации



Если вам по какой-то причине не нравится устройство маршрутизации во фреймворке, в каждом отдельном модуле вы можете создать лишь index.php файл и в нём подключить маршрутизатор по вкусу.

Поскольку index.php не требует контроллеров и структуры в index.json, вы обойдете большую часть системы маршрутизации.



Права доступа



Для каждого уровня маршрута проверяются права доступа. Права доступа во фреймворке имеют два ключевых параметра: группу и метку.

В качестве группы при проверки прав доступа к странице используется название модуля с опциональным префиксом для страниц администрирования и API, в качестве метки используется путь маршрута (без учета префикса index).



К примеру, для страницы api/Module_name/items/comments будут проверены права пользователя для разрешений (через пробел group label):




api/Module_name index
api/Module_name items
api/Module_name items/comments




Если на каком-то уровне у пользователя нет доступа — обработка завершится ошибкой 403 Forbidden, при этом обработчики предыдущих уровней не будут выполнены, так как права доступа определяются на этапе окончательного формирования маршрута, до запуска обработчиков.



Напоследок



Реализация обработки запросов в CleverStyle Framework достаточно мощная и гибкая, являясь при этом декларативной.

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

Надеюсь, данного руководства достаточно для того, чтобы не потеряться.

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



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

GitHub репозиторий: github.com/nazar-pc/CleverStyle-Framework

Документация по фреймфорку: github.com/nazar-pc/CleverStyle-Framework/tree/master/docs



Конструктивные комментарии как обычно приветствуются.



Какой подход предпочитаете вы?




























Проголосовало 6 человек. Воздержалось 3 человека.





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


Original source: habrahabr.ru.

https://habrahabr.ru/post/307690/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Доработка парсера логов Squid для корректного просмотра посещенных HTTPS ресурсов

Суббота, 14 Августа 2016 г. 02:47 (ссылка)

Всем привет! Я получал, и получаю множество писем от людей с вопросами по Squid, который работает на основе моей статьи. Наиболее часто возникает вопрос про просмотр логов Squid каким-либо парсером. Проблема в том, что версия Squid 3.5.8 с настроенным прозрачным проксированием HTTPS логирует посещаемые HTTPS ресурсы не в виде доменных имен, а в виде IP адресов с портами (прим. 164.16.43.56:443). Соответственно, при просмотре статистики посещений вместо человеческой информации проскакивают эти самые IP адреса. Собирать статистику с такими данными довольно сложно. Я связывался с разработчиками Squid по этому поводу, но внятного ответа так и не получил. Единственное, что я выяснил, нормальное логирование работает в более новых версиях Squid, но на них прозрачное проксирование лично у меня так и не заработало должным образом. Поэтому возник вопрос о том, как сделать резолв IP адресов в самом парсере логов.



Лично я пользуюсь парсером Screen Squid, и именно в нем я решил попробовать сделать нужные изменения. Так как мне подобный резолв бывает нужен просто при работе в терминале с Bash, я решил весь процесс резолва сделать в виде скрипта на Bash, а в Screen Squid уже средствами PHP его использовать, когда это будет нужно.



Итак, для всего задуманного нам нужны:




  1. собственно, сам парсер Screen Squid (инструкцию по его установке печатать не буду, все есть на оф.сайте).

  2. Grep

  3. Sed

  4. Nslookup

  5. Whois

  6. Прямые руки



Сам Bash-скрипт представляет из себя следующее:



#!/bin/bash

#Единственный входной параметр - ip адрес, запишем его в переменную
IP="$1";

#Пробуем резолвить IP адрес с помощью NSLOOKUP, применяя GREP и SED
#для извлечение из результата нужной нам информации
hostname=$(nslookup $IP | grep -m 1 "name" | sed 's|.*= ||'|sed -r 's/ Auth.+//' | sed 's/^[ \t]*//;s/[ \t]*$//' );

#Если попытка резолва с помощью NSLOOKUP не удалась,
#то узнаем информацию об IP адресе с помощью whois, опять же
#применяя GREP и SED для извлечение из результата нужной нам информации
if [[ "$hostname" == '' ]]; then
hostname=$(whois $IP | grep -m 1 "owner\|OrgName\|orgname\|NetName\|netname\|origin" | sed 's|.*: ||'|sed -r 's/. Auth.+//' | sed 's/^[ \t]*//;s/[ \t]*$//')
fi

#Выводим на экран результат резолва
echo "$hostname"

exit 0;


В принципе, он уже откомментирован, описывать здесь особенно и нечего. Мы получаем информацию об IP адресе сначала с помощью Nslookup, параллельно фильтруя вывод команды с помощью grep и sed, чтобы исключить ненужную информацию. Дабы не писать кучу строк, были использованы возможности grep по включению нескольких условий для выборки (символы "\|"). Сохраняйте скрипт в любом удобном месте, назначайте ему права на выполнение. Допустим, он сохранен в /usr/bin под именем gethost.sh.



Скрипт можно использовать просто из терминала:



gethost.sh ip_address 


Далее расскажу, как этот скрипт прикрутить к Screen Squid. Допустим, что установлен он в /var/www/html. В этой папке будет подпапка reports, где находится файл reports.php. Вот именно в нем необходимо сделать изменения. В этом файле необходимо найти строки:



$result=mysql_query($queryOneIpaddressTraffic) or die (mysql_error());
$numrow=1;
$totalmb=0;
while ($line = mysql_fetch_array($result,MYSQL_NUM)) {
echo "
".$numrow." ".$line[0]."
".$line[0]."".$hostname."
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Изменения в CleverStyle Framework 5

Четверг, 11 Августа 2016 г. 20:52 (ссылка)

Некоторое время назад вышел первый релиз ветки 5.x, а потом несколько меньших патч-версий, так что опять есть чего рассказать.

Предыдущие изменения: часть 1, часть 2, часть 3, часть 4.



Комментарии под предыдущей статьей и в чатиках были весьма полезными, отдельное спасибо fesor, который хоть и не согласен, но предоставляет конструктивную критику, которая имеет позитивные последствия.



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



Прощай jQuery



В версиях 4.x jQuery сначала была объявлена устаревшей, позже в 5.x она была полностью выпилена из фреймворка.



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



Ещё jQuery активно использовалась для XHR запросов, но с широкой поддержкой XHR2 исчезла необходимость писать несколько реализаций, и была написана функция cs.api(), которая:


  • обеспечивает 95% потребностей с гораздо более удобным синтаксисом

  • поддерживает отправку форм и файлов

  • интерфейс базируется на ES2015 Promise

  • поддерживает обработку ошибок с выводом пользователю красивых всплывающих сообщений



Некоторые модули использовали jQuery плагины, они теперь используют NPM версию jQuery.



Так же один из jQuery плагинов что использовался в ядре был отрефакторен без использования jQuery, патч принят upstream, так что фреймворк включает чистую оригинальную версию: github.com/voidberg/html5sortable/pull/204



Больше нет плагинов и шаблонов блоков



Раньше отдельно были модули и плагины. Плагины были похожи на модули, но без собственных страниц, админок, настроек, API и прочего.



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



Так же components/blocks и components/modules впоследствии перенеслись в blocks и modules по аналогии с themes.



Шаблоны блоков были достаточно кривой штукой изначально и по факту были слишком привязаны к конкретному проекту. Если добавить к этому невозможность установки блоков из пакетов — польза и вовсе сводилась к нулю.

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



Консистентность интерфейсов



Вместе с чисткой второстепенных фич было проведено много работы по консистентности внутреннего API фреймворка.



К примеру, cs\CRUD при работе с разными движками БД теперь приводит числовые типы к одному виду (MySQL даже для числовых столбцов возвращал строки, а SQLite возвращал числа).

Ещё один пример — удаление поддержки вызовов cs\DB::instance()->$db_id и cs\DB::instance()->$db_id() вместо cs\DB::instance()->db($db_id) и cs\DB::instance()->db_prime($db_id). Когда-то давно это использовалось, но это не очень удобно и усложняет вывод типов для IDE.



Множество подобных мелочей было исправлено и приведено к общему виду.



Тестирование



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



В результате общее покрытие фреймворка тестами 66%+ на момент написания статьи, в покрытие системных классов 96%+ (папка modules практически полностью состоит из контроллеров, поэтому приоритет ниже):









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



Фронтенд ещё легче, опциональное отключение поддержки веб-компонентов



Поскольку не все ещё прониклись веб-компонентами, может показаться слишком дорогим грузить Polymer, полифиллы и HTML импорты если они не будут использоваться.

Именно для этих случаев в последнем релизе поддержка веб-компонентов стала отключаемой для продвинутых пользователей в админке.



Второе важное улучшение — переход на асинхронные интерфейсы работы с переводами на фронтенде.



Раньше было так:



cs.Language.system_profile_hello('Username')
// или
cs.Language('system_profile_').hello('Username')


Теперь же нужно подождать:



cs.Language.ready().then(function (L) {
L.system_profile_hello('Username');
});
// или
cs.Language('system_profile_').ready().then(function (L) {
L.hello('Username');
});



На самом деле под капотом до 6.x оно всё ещё синхронно, но в итоге переводы будут загружаться только когда нужны.



Все эти изменения позволят уменьшить объем обязательно загружаемого JS кода с 312 КиБ до меньше чем 30 КиБ (это всё без учета gzip), а объем HTML импортов из 107 КиБ до 0 КиБ (0 файлов).

По сути, будет загружаться Alameda (RequireJS) и несколько вспомогательных системных функций/объектов.



Производительность серверной части



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



Особенно полезным является оптимизация API запросов. Существенными (относительно) накладными расходами обладала работа с переводами, большой JSON занимал как время на парсинг, так и память. Теперь же во время API запроса, не работающего с многоязычностью, переводы вообще не будут загружены в память.



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

Скрытый текст




В текстовом виде:




|framework |requests per second|relative|peak memory|relative|
|-------------------|------------------:|-------:|----------:|-------:|
|silex-1.3 | 3,029.75| 8.8| 0.59| 1.0|
|symfony-2.7 | 1,423.83| 4.2| 1.41| 2.4|
|symfony-3.0 | 995.79| 2.9| 1.64| 2.8|
|laravel-5.2 | 342.99| 1.0| 1.98| 3.4|
|zf-2.5 | 671.20| 2.0| 1.36| 2.3|
|cleverstyle-5.15 | 1,939.17| 5.7| 0.66| 1.1|




Так же выросла пиковая производительность под встроенным Http сервером вместе с отличной масштабируемостью (HHVM, 16 процессов, Core i7 4900MQ):

Скрытый текст

nazar-pc@nazar-pc /w/t/www> ab -c500 -n100000 -k http://test.com:9990/api/System/blank
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking test.com (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software: nginx/1.10.1
Server Hostname: test.com
Server Port: 9990

Document Path: /api/System/blank
Document Length: 4 bytes

Concurrency Level: 500
Time taken for tests: 9.375 seconds
Complete requests: 100000
Failed requests: 0
Keep-Alive requests: 0
Total transferred: 24400000 bytes
HTML transferred: 400000 bytes
Requests per second: 10666.64 [#/sec] (mean)
Time per request: 46.875 [ms] (mean)
Time per request: 0.094 [ms] (mean, across all concurrent requests)
Transfer rate: 2541.66 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 8 69.2 1 1013
Processing: 0 39 30.9 31 272
Waiting: 0 37 30.5 29 272
Total: 0 47 75.3 35 1199

Percentage of the requests served within a certain time (ms)
50% 35
66% 48
75% 57
80% 63
90% 83
95% 104
98% 137
99% 166
100% 1199 (longest request)




А в однопоточном режиме запросы отрабатывают начиная с 0.6 мс (0.0006 секунд), что очень достойный результат, хотя и хотелось бы забраться под 0.5 миллисекунды.



Напоследок



Как обычно, буду благодарен за конструктивные комментарии.

Репозиторий на GitHub: github.com/nazar-pc/CleverStyle-Framework
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/307626/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<php - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda