16.09.2016

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

Прежде всего стоит определить расположение файла-конфигурации пула. Если вы устанавливали php-fpm из системного репозитория, то конфигурация пула www будет расположена примерно тут /etc/php5/fpm/pool.d/www.conf . В случае если используется свой билд или другая ОС (не debian) следует поискать расположение файла в документации, или указывать его вручную.

Попробуем рассмотреть конфигурацию подробней.

Переходим на UNIX-сокеты

Наверное первое, на что следует обратить внимание, это то как проходят данные от веб-сервера к вашим php процессам. Это отражено в директиве listen:

listen = 127.0.0.1:9000

В случае если установлен адрес:порт, то данные идут через стек TCP, и это наверное не очень хорошо. Если же там путь к сокету, например:

listen = /var/run/php5-fpm.sock

то данные идут через unix-сокет, и можно пропустить этот раздел.

Почему все таки стоит перейти на unix-сокет? UDS (unix domain socket), в отличии от комуникции через стек TCP, имеют значительные преимущества:

  • не требуют переключение контекста, UDS используют netisr)
  • датаграмма UDS записываться напрямую в сокет назначения
  • отправка дейтаграммы UDS требует меньше операций (нет контрольных сумм, нет TCP-заголвоков, не производиться маршрутизация)

TCP средняя задержка: 6 us UDS средняя задержка: 2 us PIPE средняя задержка: 2 us TCP средняя пропускная способность: 253702 msg/s UDS средняя пропускная способность: 1733874 msg/s PIPE средняя пропускная способность: 1682796 msg/s

Таким образом, у UDS задержка на ~66% меньше и пропускная способность в 7 раз больше TCP. Поэтому, скорей всего стоит перейти на UDS. В моем случае сокет будет расположен по адресу /var/run/php5-fpm.sock .

; закоментируем это - listen = 127.0.0.1:9000 listen = /var/run/php5-fpm.sock

Также следует убедиться что веб-сервер (или любой другой процесс, которому необходима коммуникация) имеет доступ на чтение/запись в ваш сокет. Для этого существуют настройки listen.grup и listen.mode Проще всего - запускать оба процесса от одного пользователя или группы, в нашем случае php-fpm и веб-сервер будет запущен с группой www-data :

listen.owner = www-data listen.group = www-data listen.mode = 0660

Проверяем выбранный механизм обработки событий

Для работы с эффективной работы с I/O (вводом-выводом, дескрипторами файлов/устройств/сокетов) стоит проверить правильно ли указана настройка events.mechanism . В случае если php-fpm установлен из системного репозитория, скорей всего там все в порядке - он либо не указан (устанавливаться автоматически), либо указан корректно.

Его значение зависит от ОС, для чего есть подсказка в документации:

; - epoll (linux >= 2.5.44) ; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) ; - /dev/poll (Solaris >= 7) ; - port (Solaris >= 10)

К примеру если мы работаем на современном linux-дистрибутивe нам необходим epool:

events.mechanism = epoll

Выбор типа пула - dynamic / static / ondemand

Также, стоит обратить внимание на настройки менеджер процессов (pm). По сути это главный процесс (master process), который будет управлять всеми дочерними (которые выполняют код приложения) по определенной логике, которая собственно и описана в файле конфигурации.

Всего доступно 3 схемы управления процессами:

  • dynamic
  • static
  • ondemand

Наиболее простой - это static . Схема его работы заключается в следующем: запустить фиксированное количество дочерних процессов, и поддерживать их в рабочем состоянии. Данная схема работы не очень эффективна, так как количество запросов и их нагрузка может меняться время от времени, а количество дочерних процессов нет - они всегда занимают определенный объем ОЗУ и не могут обрабатывают пиковые нагрузки в порядке очереди.

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

ondemand пул очень похож на static , но он не запускает дочерних процессов при старте главного процесса. Только когда придет первый запрос - будет создан первый дочерний процесс, и по истечении определенного времени ожидания (указанного в конфигурации) он будет уничтожен. Потому он актуален для серверов с ограниченными ресурсами, или той логики которая не требует быстрой реакции.

Утечки памяти и OOM killer

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

  • pm.max_requests
  • request_terminate_timeout

pm.max_requests это максимальное количество запросов, которое обработает дочерний процесс, прежде чем будет уничтожен. Принудительное уничтожение процесса позволяет избежать ситуации в которой память дочернего процесса “разбухнет” по причине утечек (т.к процесс продолжает работу после от запроса к запросу). С другой стороны, слишком маленькое значение приведет к частым перезапускам, что приведет к потерям в производительности. Стоит начать с значения в 1000, и далее уменьшить или увеличить это значение.

request_terminate_timeout устанавливает максимальное время выполнения дочернего процесса, прежде чем он будет уничтожен. Это позволяет избегать долгих запросов, если по какой-либо причине было изменено значение max_execution_time в настройках интерпретатора. Значение стоит установить исходя из логики обрабатываемых приложений, скажем 60s (1 минута).

Настройка dynamic пула

Для основного сервера приложения, ввиду явных преимуществ, часто выбирают dynamic пул. Его работа описана следующими настройками:

  • pm.max_children - максимальное количество дочерних процессов
  • pm.start_servers - количество процессов при старте
  • pm.min_spare_servers - минимальное количество процессов, ожидающих соединения (запросов для обработки)
  • pm.max_spare_servers - максимальное количество процессов, ожидающих соединения (запросов для обработки)

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

  • сколько памяти в среднем потребляет дочерний процесс
  • объем доступного ОЗУ

Выяснить среднее значение памяти на один php-fpm процесс на уже работающем приложении можно с помощью планировщика:

# ps -ylC php-fpm --sort:rss S UID PID PPID C PRI NI RSS SZ WCHAN TTY TIME CMD S 0 1445 1 0 80 0 9552 42588 ep_pol ? 00:00:00 php5-fpm

Нам необходимо среднее значение в колонке RSS (размер резидентной памяти в килобайтах). В моем случае это ~20Мб. В случае, если нагрузки на приложения нет, можно использовать Apache Benchmark, для создания простейшей нагрузки на php-fpm.

Объем общей / доступной / используемой памяти можно посмотреть с помощью free :

# free -m total used free ... Memory: 4096 600 3496

Total Max Processes = (Total Ram - (Used Ram + Buffer)) / (Memory per php process) Всего ОЗУ: 4Гб Используется ОЗУ: 1000Мб Буфер безопасности: 400Мб Память на один дочерний php-fpm процесс (в среднем): 30Мб Максимально возможное кол-во процессов = (4096 - (1000 + 400)) / 30 = 89 Четное количество: 89 округлили в меньшую сторону до 80

Значение остальных директив можно установить исходя из ожидаемой нагрузки на приложение а также учесть чем еще занимается сервер кроме работы php-fpm (скажем СУБД также требует ресурсов). В случае наличия множества задач на сервере - стоит снизить к-во как начальных / максимальных процессов.

К примеру учтем что на сервере находиться 2 пула www1 и www2 (к примеру 2 веб-ресурса), тогда конфигурация каждого из них может выглядеть как:

pm.max_children = 40 ; 80 / 2 pm.start_servers = 15 pm.min_spare_servers = 15 pm.max_spare_servers = 25

1. GROUP BY one key

This function works as GROUP BY for array, but with one important limitation: Only one grouping "column" ($identifier) is possible.

Function arrayUniqueByIdentifier(array $array, string $identifier) { $ids = array_column($array, $identifier); $ids = array_unique($ids); $array = array_filter($array, function ($key, $value) use($ids) { return in_array($value, array_keys($ids)); }, ARRAY_FILTER_USE_BOTH); return $array; }

2. Detecting the unique rows for a table (twodimensional array)

This function is for filtering "rows". If we say, a twodimensional array is a table, then its each element is a row. So, we can remove the duplicated rows with this function. Two rows (elements of the first dimension) are equal, if all their columns (elements of the second dimension) are equal. To the comparsion of "column" values applies: If a value is of a simple type , the value itself will be use on comparing; otherwise its type (array , object , resource , unknown type) will be used.

The strategy is simple: Make from the original array a shallow array, where the elements are implode d "columns" of the original array; then apply array_unique(...) on it; and as last use the detected IDs for filtering of the original array.

Function arrayUniqueByRow(array $table = , string $implodeSeparator) { $elementStrings = ; foreach ($table as $row) { // To avoid notices like "Array to string conversion". $elementPreparedForImplode = array_map(function ($field) { $valueType = gettype($field); $simpleTypes = ["boolean", "integer", "double", "float", "string", "NULL"]; $field = in_array($valueType, $simpleTypes) ? $field: $valueType; return $field; }, $row); $elementStrings = implode($implodeSeparator, $elementPreparedForImplode); } $elementStringsUnique = array_unique($elementStrings); $table = array_intersect_key($table, $elementStringsUnique); return $table; }

It"s also possible to improve the comparing, detecting the "column" value"s class, if its type is object .

The $implodeSeparator should be more or less complex, z.B. spl_object_hash($this) .

3. Detecting the rows with unique identifier columns for a table (twodimensional array)

This solution relies on the 2nd one. Now the complete "row" doesn"t need to be unique. Two "rows" (elements of the first dimension) are equal now, if all relevant "fields" (elements of the second dimension) of the one "row" are equal to the according "fields" (elements with the same key).

The "relevant" "fields" are the "fields" (elements of the second dimension), which have key, that equals to one of the elements of the passed "identifiers".

Function arrayUniqueByMultipleIdentifiers(array $table, array $identifiers, string $implodeSeparator = null) { $arrayForMakingUniqueByRow = $removeArrayColumns($table, $identifiers, true); $arrayUniqueByRow = $arrayUniqueByRow($arrayForMakingUniqueByRow, $implodeSeparator); $arrayUniqueByMultipleIdentifiers = array_intersect_key($table, $arrayUniqueByRow); return $arrayUniqueByMultipleIdentifiers; } function removeArrayColumns(array $table, array $columnNames, bool $isWhitelist = false) { foreach ($table as $rowKey => $row) { if (is_array($row)) { if ($isWhitelist) { foreach ($row as $fieldName => $fieldValue) { if (!in_array($fieldName, $columnNames)) { unset($table[$rowKey][$fieldName]); } } } else { foreach ($row as $fieldName => $fieldValue) { if (in_array($fieldName, $columnNames)) { unset($table[$rowKey][$fieldName]); } } } } } return $table; }

PHP в полной мере можно назвать «народным языком веб-программирования», очень сложно найти другой такой язык, который бы мог на равных соперничать с ним по уровню своей популярности. Я решил поближе узнать людей стоящих за его разработкой, ответить на острые вопросы, обсудить последние новинки, и даже попробовать заглянуть в будущее Интернета. Для этого мы встретились с легендарными участниками PHP Core Team , которые непосредственно курируют разработку этого языка и заслужили репутацию признанных мировых экспертов в области веб-технологий.

Итак, сегодня на мои вопросы отвечают ведущие разработчики из PHP Core Team — (Andrei Zmievski), (Stas Malyshev), (Ilia Alshanetsky).

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

СM: Сейчас я живу в США и работаю в фирме , занимаюсь инфраструктурой этого продукта — т.е. теми частями системы, которые обеспечивают базу для общей функциональности и предоставляют интерфейс для разработки конкретных модулей.

Если же говорить об истории моего прихода в программирование, то я начал ещё в школе, с и с программ, опубликованных в журнале «Техника — Молодёжи ». Хотя поначалу это нельзя было назвать программированием — я в основном просто вносил программы, напечатанные в журнале, в память машины и смотрел, что получается. Но потом мне захотелось самому научиться заставлять эту сложную (по тем временам) машину делать то, что я хочу, и я стал разбираться — самостоятельно и с помощью старших — как же это всё работает. Участвовал в кружках, просиживал часами в библиотеке за чтением иностранных журналов, писал программы для своего удовольствия, а потом — и за деньги. Оказалось, что заставлять компьютеры приносить пользу мне нравится, и к моей большой удаче, такое умение пользуется неплохим спросом сегодня на рынке труда.

Перечислить всё, что на меня повлияло на этом пути, будет трудно, но самый большой эффект на меня, наверное, оказали книги, в частности работы , («Мифический человеко-месяц») и (The Inmates Are Running the Asylum — в русском переводе «Психбольница в руках пациентов»).

АЗ: Уже 5 лет я живу в Сан-Франциско и считаю, что это самый красивый город в Америке. Работаю в компании . Хоть это относительно и молодой стартап, у нас уже где-то 110-120 сотрудников. Самые разные фирмы по всему миру используют наш продукт для мониторинга своих бизнес-систем и для быстрого нахождения проблем. Есть у нас и крупные клиенты, например, такие как Netflix, Priceline и другие.

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

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

Очнулся только тогда, когда этот центр уже закрывался, в 10 часов вечера. К тому времени родители уже обзвонили всех моих друзей, учителей, и т.д. в поисках меня, но к счастью — наткнулись на меня возвращающегося с центра. Мне за это тогда сильно попало, но, оглядываясь назад, я понимаю: сколько себя помню, я всегда писал программы и жил в этом отчасти виртуальном мире, поэтому моя теперешняя профессия — это просто продолжение увлекательного путешествия начатого ещё в детстве.

ИА: Я живу и работаю в Торонто, Канада.

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

К сожалению, по мере роста данного проекта на Perl, мне всё больше не нравилась непоследовательность этого языка, и вот «замученная» мною хостинговая компания, которая обслуживала меня в то время, однажды сама предложила попробовать новый для того времени PHP. Этот язык сразу же понравился, потому что он был лучше структурирован и ближе по своему синтаксису к моему любимому Си. Отличная онлайн-документация — завершила мой быстрый и окончательный переход на него.


Илья Альшанетский (Ilia Alshanetsky)

Я настолько активно стал пользоваться PHP 3, что постепенно стали появляться собственные наработки, расширявшие его возможности, которые стал регулярно предлагать в IRC-канале его создателю Расмусу. Патчей и исправлений от меня было так много, что ему это быстро надоело, и он дал мне прямой CVS-доступ к репозиторию проекта. С тех пор было очень много чего написано и сделано, мне даже довелось быть релиз-менеджером (PHP release master) для его популярных версий 4.3, 5.1 и 5.2.

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

Как вы попали в PHP Core Team, какой путь для этого прошли?

АЗ: Позвольте, отвечу на своём примере, но сначала немного истории. Я начал работать с PHP где-то в 1998 году, тогда это был PHP 3, одна из ранних версий. Мы переписывали большой проект с другого языка на PHP, и мне нужно было найти поддержку теперь уже почти забытого . Я не нашёл такой библиотеки, но мне очень понравилось что у PHP была отличная документация и API, поэтому я решил написать своё расширение. Когда закончил, сразу отправил его в почтовую рассылку PHP. А они неожиданно для меня решили добавить его к следующему релизу языка.

Впоследствии мне дали доступ к CVS, и я стал чинить баги, писать больше разных функций и т.д. К концу 1999 г. я был одним из самых активных членов группы, и тогда предложил организовать первый съезд разработчиков PHP в Израиле, чтобы обсудить будущее языка, и включил меня в эту рассылку. Так что никакого голосования не было, просто так получилось, что я сразу попал в PHP Core Team .


Расмус Лердорф (Rasmus Lerdorf), создатель PHP

Такое доверие и открытость мотивировали меня работать больше и больше над кодом PHP. После WDDX я работал над session-модулем, потом написал кучу функций для работы с массивами, затем решил, что PHP надо иметь регулярные выражения подобные Perl, поэтому создал PCRE модуль. К тому времени PHP 4 уже давно вышел, но я меня очень раздражали некоторые проблемы с тем как работали (или вернее не работали) объекты и классы. По этому поводу у меня были долгие переписки с Andi и Zeev, которые создали , и из этого вылилась принципиально новая поддержка Object-Oriented Programming в тогда новом PHP 5.

После этого я работал в основном над кодом для и самим Zend Engine, также сделал новый tokenizer-модуль. С 2003 года вместе с Расмусом, и там я запустил проект по поддерже Unicode в PHP, но это — для другого раза...

Что, по вашему мнению, есть такого в PHP, что сделало его столь популярным?

АЗ: Так получилось, что в данный момент я пишу проект на C++ и Python, так что отвечу исходя из этого сравнительного опыта, смотря как бы с этой «другой колокольни».

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

Ошибки, связанные с безопасностью — это основная головная боль для веб-программистов и многочисленных пользователей их продуктов. Оглядываясь на свой богатый опыт, какие самые главные и типичные проблемы с безопасностью у PHP и его разработчиков вы можете выделить? Можете дать какие-то советы в назидание молодым PHP-программиcтам?

СM: Один из главных уроков, мне кажется, состоит в том, что безопасность кода — это не качество, которое можно привить внешне, независимо от остального кода. Забота о безопасности должна быть повседневной частью работы, с самой первой минуты, начиная с дизайна и до выпуска готового продукта. В PHP было предпринято несколько попыток добавить средства безопасности «извне» — но опыт показал, что эти попытки, без постоянной бдительности со стороны программиста, обречены на провал. Платформа может только помочь ему следить за безопасностью кода, но не может гарантировать эту безопасность без усилий с его стороны.

Поэтому главный совет будет следующим — никогда не доверять никаким внешним данным, или данным, контроль над которыми возможен извне. 99% проблем в безопасности происходят оттого, что допущения, сделанные о входных данных, оказываются неверными, и когда впоследствии происходит атака, то код ведёт себя не так, как это задумал сам разработчик. Поэтому развивайте у себя привычку спрашивать себя всегда — «что, если этот параметр изменить неожиданным образом? Что, если этот аргумент применить не так, как предполагалось?» И тестируйте свой код соответственно.

Следующая моя рекомендация — вместо проверки «нет ли в данных чего-то недозволенного», превентивно преобразуйте данные так, чтобы они были гарантировано такими, как надо. Так будет гораздо проще избежать ошибок. Также не полагайтесь на то, что заботу о безопасности возьмут на себя другие части кода — так, например, если вы делаете динамический запрос к базе данных, всегда самостоятельно убеждайтесь, что параметры и запрос генерируются и фильтруются таким образом, что sql injection невозможна — даже если вы уверены, что данные где-то ещё фильтруются (например, на уровне фреймворка). Тогда, если внешний код изменят таким образом, что в вашу процедуру вдруг попадут нефильтрованые данные, уязвимости всё равно не возникнет.

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

В общем, браузер окончательно станет полноправной частью веб-инфраструктуры, в связи с чем часть обязанностей, исполняемых сейчас сервером, перейдёт на сторону клиента. Поэтому я бы посоветовал людям, работающий сейчас с PHP, заодно следить за новейшими достижениями в области Javascript, HTML5, CSS3 и т.п.

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

Действующие лица:


Андрей Змиевский , один из кураторов PHP Project , разработчик Zend Engine, создатель проектов и , соавтор популярного учебника , автор реализаций Unicode и OOP в PHP. В свободное время увлекается спортом, пивоварением на дому, готовкой. Очень любит много читать и путешествовать.

Стас Малышев , участник PHP Project с 2000 года, релиз-менеджер версии PHP 5.4, участник многих OpenSource-проектов. В свободное время интеллектуальными играми (спортивная версия «Что? Где? Когда?», «Брейн-ринг») и айкидо.

Илья Альшанетский , эксперт в области безопасности и один из старейших разработчиков PHP, глава , релиз-менеджер версий PHP 4.3, 5.1, 5.2, создатель форумного движка , активный участник множества OpenSource-проектов. и на тему безопасности веб-программирования.

p.s.: Обещаю, что в новом 2013 году выложу ещё множество своих VIP-интервью с ведущими мировыми разработчиками, — пусть какая-то конкретика пока будет секретом.

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

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

Перейдем к конкретной «ходовой» задаче - объектная прослойка для работы с базами данных в PHP. Решений великое множество, начиная от PDO и заканчивая многоуровневыми (и, на мой взгляд, не совсем уместными в PHP) ORM движками.

Большинство этих решений перекочевали в PHP из других платформ. Но зачастую авторы не учитывают особенности PHP, которые позволили бы резко упростить как написание, так и использование портируемых конструкций.
Одной из распространенных архитектур для данного класса задач является паттерн Active Record. В частности, по этому шаблону строятся так называемые Entity (сущности), в том или ином виде использующиеся в ряде платформ, начиная от персистентных бинов в EJB3 заканчивая EF в.NET.

Итак, построим подобную конструкцию для PHP. Соединим между собой две клёвые штуки - готовую библиотеку ADODB и слаботипизированность и динамические свойства объектов языка PHP.
Одной из многочисленных фич ADODB является так называемая автогенерация SQL запросов для вставки (INSERT) и обновления (UPDATE) записей на основе ассоциативных массивов с данными.
Собственно нет ничего военного взять массив, где ключи - имена полей а значения - соответственно, данные и сгенерировать строку SQL запроса. Но ADODB делает это более интеллектуально. Запрос строится на основе структуры таблицы, которая предварительно считывается с схемы БД. В результате во-первых, в sql попадают только существующие поля а не всё подряд, во-вторых, учитывается тип поля - для строк добавляются кавычки, форматы дат могут формироваться на основе timestamp если ADODB видит оный вместо строки в передаваемом значении и т.д.

Теперь зайдем со стороны PHP.
Представим такой класс (упрощенно).

Class Entity{ protected $fields = array(); public final function __set($name, $value) { $this->fields[$name] = $value; } public final function __get($name) { return $this->fields[$name]; } }

Передавая внутренний массив библиотеке ADODB мы можем автоматически генерировать SQL запросы для обновления записи в БД данным объектом, При этом, не нужны громоздкие конструкции маппинга полей таблиц БД на поля объекта сущности на основе XML и тому подобного. Нужно только чтобы имя поля совпадало со свойством объекта. Поскольку то, как именуются поля в БД и поля объекта, для компьютера значения не имеет, то нет никаких причин чтобы они не совпадали.

Покажем, как это работает в конечном варианте.
Код законченного класса расположен на Gist . Это абстрактный класс, содержащий минимум необходимого для работы с БД. Отмечу, что данный класс - упрощенная версия решения, отработанного на нескольких десятках проектов.

Представим, что у нас такая табличка:

CREATE TABLE `users` (`username` varchar(255) , `created` date , `user_id` int(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`user_id`))
Тип БД не имеет значения - ADODB обеспечивает переносимость для всех распространенных серверов БД.

Создадим класс сущности Пользователь, на основе класса Entity

/** * @table=users * @keyfield=user_id */ class User extends Entity{ }

Собственно и все.
Используется просто:

$user = new User(); $user->username="Вася Пупкин"; $user->created=time(); $user->save(); //сохраняем в хранилище // загрузим опять $thesameuser = User::load($user->user_id); echo $thesameuser ->username;

Таблицу и ключевое поле указываем в псевдоаннотациях.
Также можем указать представление (к примеру, view =usersview) если, как это часто бывает, сущность выбирается на основе своей таблицы с приджойнеными или вычисляемыми полями. В этом случае выбираться данные будут из представления а обновляться будет таблица. Кому не нравятся такие аннотации могут переопределить метод getMetatada() и указать параметры таблицы в возвращаемом массиве.

Что еще полезного представляет класс Entity в данной реализации?

Например, мы можем переопределить метод init(), который вызывается после создания экземпляра Entity, чтобы инициализировать дату создания по умолчанию.
Или перегрузить метод afterLoad(), который автоматически вызывается после загрузки сущности из БД, чтобы преобразовать дату в timestamp для дальнейшего более удобного использования.
В результате получим не намного более сложную конструкцию.

/** * @table=users * @view=usersview * @keyfield=user_id */ class User extends Entity{ protected function init() { $this->created = time(); } protected function afterLoad() { $this->created = strtotime($this->created); } }

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

Загружаем список сущностей по критерию (по сути условия для WHERE).
$users = User::load("username like "Пупкин" ");
Также класс Entity позволяет выполнить произвольный, «нативный» так сказать SQL запрос. Например, мы хотим вернуть список пользователей с какими нибудь группировками по статистике. Какие конкретно поля вернутся, не имеет значения (главное чтобы там было user_id, если есть необходимость дальнейшей манипуляции сущностью), нужно только знать их наименования чтобы обратится к выбранным полям. При сохранении сущности, как очевидно из вышеприведенного, тоже не надо заполнять все поля, какие будут присутствовать в объекте сущности, те и пойдут в БД. То есть нам не нужно создавать дополнительные классы для произвольных выборок. Примерно как анонимные структуры при выборке в EF только здесь это тот же класс сущности со всеми методами бизнес-логики.

Строго говоря, вышеприведенные методы получения списков несколько выходят за пределы паттерна AR. По сути - это фабричные методы. Но как завещал старик Оккама, не будем плодить сущности сверх необходимого и городить отдельный Entity Manager или типа того.

Заметим что вышеприведенное - просто классы PHP и их можно как угодно расширять и модифицировать, дописывать в сущности (или базовый класс Entity) свойства и методы бизнес-логики. То есть мы получаем не просто копию строки таблицы БД, а именно бизнес-сущность как часть объектной архитектуры приложения.

Кому это может быть полезно? Разумеется, не прокачанным разрабам, которые считают что использование чего то проще доктрины - не солидно, и не перфекционистам, уверенным, что если решение не вытягивает миллиард обращений к Бд в секунду то это не решение. Судя по форумам, перед многими обычными разработчиками, работающими над обычными (коих 99.9%) проектами рано или поздно возникает проблема найти простой и удобный объектный способ для доступа к БД. Но сталкиваются с тем, что большинство решений либо неоправданно наворочены, либо являются частью какого-либо фреймворка.

P.S. Вынес решение из фреймворка отдельным проектом

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

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

В 2009 году разработчики нескольких фреймворков договорились о создании сообщества PHP Framework Interop Group (PHP-FIG) , которое бы вырабатывало рекомендации для разработчиков. Важно подчеркнуть, что речь не идет о ISO-стандартах , более правильно говорить о рекомендациях. Но так как создавшие PHP-FIG сообщество разработчики представляют крупные фреймворки, то их рекомендации представляют серьёзный вес. Поддержка PSR (PHP standart recommendation) стандартов позволяет обеспечивать совместимость, что облегчает и ускоряет разработку конечного продукта.

Всего на момент написания статьи существует 17 стандартов, причем 9 из них являются утвержденными, 8 находятся в стадии проекта, активно обсуждаются, 1 стандарт не рекомендован к использованию.

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

PSR-1. Основной стандарт кодирования

Он представляет собой наиболее общие правила, такие как, например, использование тегов PHP , кодировка файлов, разделения места объявления функции, класса и места их использования, именование классов, методов.

PSR-2. Руководство по стилю кода

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

PSR-3. Интерфейс протоколирования.

Этот стандарт разработан для того, чтобы обеспечить (журналирование) логирование в приложениях, написанных на PHP .

PSR-4. Стандарт автозагрузки

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

PSR-6. Интерфейс кеширования

Кэширование используется для повышения производительности системы. И PSR-6 позволяет стандартно сохранять и извлекать данные из кэша, используя унифицированный интерфейс.

PSR-7. Интерфейс HTTP-сообщений

При написании мало-мальски сложных сайтов на PHP , почти всегда приходиться работать с HTTP заголовками . Конечно, язык PHP предоставляет нам уже готовые возможности для работы с ними, такие как суперглобальный массив $_SERVER , функции header() , setcookie() и т.д., однако их ручной разбор чреват ошибками, да и не всегда можно учесть все нюансы работы с ними. И вот, чтобы облегчить работу разработчику, а также сделать единообразным интерфейс взаимодействия с HTTP протоколом был принят данный стандарт. Более подробно об этом стандарте я расскажу в одной из следующих статей.

PSR-11. Интерфейс контейнера

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

PSR-13. Гипермедиа ссылки

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

PSR-14. Интерфейс простого кэширования

Является продолжением и улучшением стандарта PSR-6

Таким образом, сегодня мы с Вами рассмотрели PSR стандарты . За актуальной информацией о статусе стандартов можете обращаться по адресу


Close