В отличие от php — нам не нужно реализовывать систему кэширования классов и т.п. — все это хранится в памяти и инициализируется только один раз (при старте приложения).
Однако в процессе портирования Symfony2 на node.js возник ряд сложностей, связанных с асинхронном моделью node.js, а также с тем, что массивы и хэши в javascript устроены не одинаково (в отличие от php).
Кому интересно что получилось — прошу под кат.
Надеюсь никто не расценит мой поступок высокомерным или плагиатом, но проект я решил назвать JSymfony. На мой взгляд название отлично отображает обе концепции проекта — JS и Symfony.
Расммотрим наконец этапы разработки, состояние проекта и планы на ближайшее время.
Примечание 1. Примеры кода по различным компонентам в статье приводить не буду, иначе статья займет очень много места. Примеры доступны в соответствующих репозиториях в папке examples
Примечание 2. В посте я буду использовать слово класс вместо функция-конструктор да простят меня гуру javascript (подождем истинной реализации слова class в harmony ecmascript)
Примечание 3. В переписываемых компонентах я старался по максимуму прописывать JSdoc у классов и методов
Примечание 4. В связи с очень большим объемом кода, времени на написание тестов и грамотных README файлов пока что не было. Надеюсь мне помогут с этим заинтересованные люди
Примечание 5. Статья рассчитана на тех, кто понимает хотя бы примерно как устроена Symfony2 изнутри
1. Реализация системы пространств имен (и автозагрузки)
Да, я люблю node.js за скорость, но мне не нравится их подход с системой модулей. Система модулей отлично подходит для небольших проектов, но многие разработчики начинают писать абсолютно не расширяемый код. Чтобы не быть голословным — в конце статьи приведены примеры не расширяемого кода.
Моей идеей было внедрить в node.js аналог пространств имен (namespace). Чтобы в любом коде программы можно было писать
var router = new JSymfony.Routing.Router(); var fileLocator = new JSymfony.Config.FileLocator(__dirname); //вместо var JSymfonyRouting = require('jsymfony-routing'); var JSymfonyConfig = require('jsymfony-config') var router = JSymfonyRouting.Router(); var fileLocator = JSymfonyConfig.FileLocator(__dirname); // или вместо var router = new (require('jsymfony-routing').Router)(); var fileLocator = new (require('jsymfony-config').FileLocator)(__dirname);
Кроме того первый вариант гораздо лучше подсвечивается в IDE (я использую phpStorm) и позволяет перейти к файлу с определением «класса» Router, нажав на слово Router в этой строке: var router = new JSymfony.Routing.Router() (в варианте require(‘smth’).Foo.Bar перейти к файлу, в котором описывается Bar часто бывает очень проблематично).
Итогом работы стала реализация модуля, позволяющего легко имитировать namespace в javascript. Исходный код модуля доступен здесь: github.com/jsymfony/autoload
2. Реализация базового модуля
Следующим шагом стало создание базового модуля, содержащего часто используемые функции и стандартные классы ошибок (например RuntimeError, InvalidArgumentError и т.п.) — кто хоть раз пытался наследоваться от стандартного Error, чтобы корректно работал instanceof и стеки — поймут. Также есть класс FunctionReflect, позволяющий из описания функции получить имена аргументов функции (нужно при вызове методов контроллеров с параметрами из роутинга).
Код базового модуля доступен здесь: github.com/jsymfony/base
3. Реализация компонента Config
Следующим этапом стало непосредственное переписывание компонента Config из Symfony2, так как он используется в компоненте Dependency Injection, который в конечном итоге нам и нужен. На этом шаге пришлось столкнуться с проблемой в разном механизме работы с массивами и хэшами в javascript.
Код доступен тут: github.com/jsymfony/config
4. Dependency Injection Component
На мой взгляд самый важный компонент в Symfony2. Именно этот компонент отвечает за наполнение нашего контейнера сервисами и параметрами. Самая большая проблема при написании модуля возникла при реализации scope в контейнере.
Symfony2 (что такое scope и зачем они — тут: symfony.com/doc/2.0/cookbook/service_container/scopes.html)
Поскольку php работает синхронно, то в каждый момент времени контейнер может находится только в одном из scope. Поэтому разработчики Symfony2 при входе в новый scope просто заносят конфликтующие сервисы в стек, а при выходе восстанавливают их обратно. Но в node.js наш DI контейнер может находится одновременно сразу в нескольких scope, поэтому физически невозможно использовать один экземпляр контейнера при работе в node.js. В итоге был реализован механизм, корректно работающий со scope в node.js
Код компонента доступен тут: github.com/jsymfony/dependency-injection
5. Routing
Очередным этапом переписывания компонентов Symfony2 стал компонент Routing. Как обычно пришлось столкнуться с определенными проблемами, на этот раз связанными в основном с реализацией регулярных выражений (нет поддержки именованных параметров и нет поддержки possessive quantifiers).
Код компонента доступен тут: github.com/jsymfony/routing
Текущее состояние и планы
Есть частичная реализация бандлов
framework (сердце Symfony2 — именно тут и происходит вся магия по запуску приложения, инициализации контейнера, обработки запросов)
passportjs (аналог security в Symfony2 и опирающийся на passportjs.org/)
cache (в качестве драйверов реализованы memory и memcache)
cluster (обертка вокруг cluster модуля node.js, добавляющая несколько методов для более удобной коммуникации между master и workerами)
mysql (обертка вокруг github.com/felixge/node-mysql, реализующая также пул коннектов и возможность иметь под одним именем несколько slave и master коннектов)
Коды этих бандлов пока что не находятся в репозитории, т.к. нужно для начала причесать там код, т.к. изначально код писался для себя и в нем почти нет комментариев.
В ближайшее время необходимо реализовать Templating компонент, после чего станет возможным писать полноценные приложения и дорабатывать остальной функционал.
Заключение
Проделано много работы по портированию Symfony2 на node.js. К сожалению одному это делать довольно сложно, поэтому предлагаю заинтересовавшимся помочь мне в этом нелегком деле.
Забегая вперед
Наверняка сейчас начнутся комментарии «зачем еще один фреймворк», почему бы не использовать «express» и т.п. Поэтому сразу отвечу
1. Ни один фреймворк для node.js, который я встречал, не обладает такой гибкостью и лаконичностью как Symfony2 для php.
2. Я ни в коем случае не хочу никому навязывать свое мнение, но для меня очень популярный express.js (и connect.js) обладают рядом больших минусов, таких как:
а) невозможность декорировать функцию next в middleware (т.е. написать профайлер запроса станет невозможным)
б) конфиги для middleware часто дублируют друг друга (например для cookieParse и session нужно два раза указывать один и тот же secret). Кроме того нет возможности добавить функционал в текущий middleware (очень хотелось вместо boolean параметра trusted_proxy иметь список ip адресов для trusted_proxies), например, посмотрите это: github.com/senchalabs/connect/blob/master/lib/middleware/session.js
в) функция get одновременно работает и как часть роутера и как getter параметров (на мой взгляд это не самый лучший подход — иметь два абсолютно разных поведения для функции).
г) при передаче параметров в шаблоны эти параметры смешиваются с настройками движка шаблонов, что иногда приводит к неочевидным проблемам
ссылка на оригинал статьи http://habrahabr.ru/post/168177/
Добавить комментарий