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

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

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

Массивы

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

Простой массив

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

Внутренности массивов php - hashtable

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

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

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

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

Структуры

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

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

Множества

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

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

Куча

Куча

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

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

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

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

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

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

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

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

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

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


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

Пример дебага без break point

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

function ErrorHandler($errno, $errstr, $errfile, $errline) { if (!in_array($errno,array(E_NOTICE,2048))) { xdebug_break(); restore_error_handler(); trigger_error($errno.$errstr." in ".$errfile." on line ".$errline."; showed by error handler "); } } function shutDownFunction() { if(!is_null($e = error_get_last())) xdebug_break();} function exceptionHandler($exception) { xdebug_break(); restore_exception_handler();} set_error_handler('ErrorHandler'); register_shutdown_function('shutdownFunction'); set_exception_handler('exceptionHandler');

FastCGI process manager для PHP

Набрёл на интересную презентацию Андрея Нигматулина на phpconf08, которій рассказывает о проблемах PHP на высоких нагрузках. О apache, nginx, eaccelerator, fastcgi и своём менеджере php-процессов.

BRMS на php с отражениями

Отражения (Reflection API) в php — мощный инструмент для самоанализа кода. Давно не писал ничего интересного, а тут такая интересная мини-задачка - написать маленькую систему бизнес-правил aka BRMS для обработки сложных форм, причём не просто десять табов который сохраняются в БД, а анализ который приводит к каким-то выводам.

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

Как я выше написал, анализ проводится с помощью малодокументированными но вполне рабочими отражениями. В итоге примерно такой код..
$oRuleContainer = new cRuleContainer(); //просто класс с методами-правилами $rContainer = new ReflectionClass('cRuleContainer'); //отражение класса //где-то тут цикл по вызываемым методам, можно проходится по всем //но я проходил по методам из базы, поэтому его опускаю.. тут появляется $aRule $rMethod = $rContainer->getMethod($aRule['method']); $aArgs = $rMethod->getParameters(); //выбираем только нужные аргументы if($aArgs){ foreach($aArgs as $refArgument){ $arrPassedArgData[$refArgument->name]=$_POST[$refArgument->name]; } } if(call_user_func_array(array($oRuleContainer,$aRule['method']),$arrPassedArgData)){ //правило сработало }
Кстати, я прекрасно понимаю что можно вызывать правила без ничего, читая всё из POST, но тут решение эстетическое и повторно используемое.

PHP frameworks

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

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

codeigniter-Web-PHP-framework.png

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

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

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

symfony-Web-PHP-framework.png

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

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

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

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

cakePHP-Web-PHP-framework.png

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

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

zend-Web-PHP-framework.png

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

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

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

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

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

akelos-Web-PHP-framework.png

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

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

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

yii-Web-PHP-framework.png

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

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

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

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