Pull to refresh

Аутичный onPHP: обзор

Reading time 10 min
Views 7K
Приветствую, хабралюди.

onPHP OnPHP — это фреймворк, как не трудно догадаться, написанный на PHP. Как и любой подобный инструмент он обладает своими преимуществами и недостатками и, как я считаю, первых у него больше.

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



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

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

Введение.


Фреймворк родился и вырос в команде людей, которая создавала его и развивала для собственных нужд, насколько я понял, с 2005-го года. Самая ранняя версия, доступная публично, это версия 0.2, датированная 3 сентября 2005 года. С тех пор фреймворк сильно изменялся, в него добавлялись новые возможности и исправлялись ошибки. Сейчас onPHP — это достойная кандидатура на роль фундамента для веб-проекта любого масштаба. Последняя стабильная версия — 1.0.

Достоинства и недостатки.


Как я уже говорил, у любого инструмента (а скорее просто — у любого) есть свои достоинства и недостатки, и onPHP не исключение. Среди достоинств я смог выделить следующие:

1. Быстрая разработка приложений.

Инструменты onPHP позволяет очень быстро создать нужные классы и связи между ними, построить структуру БД и проверить зависимости. Все это делается с помощью схемы в формате XML, описывающей сущности проекта. Подробнее об этом ниже. Кроме того, имеется удобные инструменты для доступа к данным: Criteria (обдуманный порт из Hibernate) и OSQL.

2. Горизонтальная масштабируемость.

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

3. Гибкость.

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

4. Скорость.

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

5. Качество и надежность.

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

6. Качественный, чистый и понятный код.

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

Пожалуй из достоинств это все. Недостатки:

1. Отсутствие документации.

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

2. Небольшое сообщество.

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

3. Конфликт классов.

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

С чего начать?


В мастер-ветке git'а, есть очень краткий tutorial, в котором есть рекомендации по типовой структуре проекта, точке входа (index.php), использованию базы данных, использованию форм (мощный инструмент для приема и валидации входных данных) и недописанная заметка про ACL, поскольку он еще в инкубаторе.

Инкубатор.


Эта та часть onPHP, в которой находятся те части фрейморка, которые официально еще не приобрели окончательной формы и в которых возможны изменения, неработающий функционал и прочие беспорядки. Здесь ведется разработка, отладка и тестирование. Новые части фрейморвка проходят через разработку в инкубаторе, а после стабилизации попадают в основной раздел.
Так, в инкубаторе на текущий момент находятся Acl, Tsearch (полнотекстовый поиск для PostgreSQL), FormRendering (централизованное отображение форм — объекта Form и его примитивов), Application (приложение и совокупность его настроек для удобной настройки) и еще несколько пакетов.

Meta.


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

Пример схемы:
<?xml version="1.0"?>
<!DOCTYPE metaconfiguration SYSTEM "meta.dtd">
<metaconfiguration>
<classes>
        <class name="Author" table="authors">
                <properties>
                        <identifier/>
                        <property name="name" type="String" size="50" required="true"/>
                </properties>
                <pattern name="StraightMapping"/>
        </class>
        <class name="Book" table="books">
                <properties>
                        <identifier/>
                        <property name="title" type="String" size="50" required="true"/>
                        <property name="author" type="Author" relation="OneToOne" required="true" fetch="lazy"/>
                </properties>
                <pattern name="StraightMapping"/>
        </class>
</classes>
</metaconfiguration>

Что здесь интересного:

— Для каждой сущности указывается какими свойствами она обладает и какой их тип. На основе этого, meta-builder порекомендует какие изменения в схеме БД нужно сделать, а также этим можно воспользоваться для валидации данных объектов по форме (Form).

— Можно сразу указать связи между сущностями, которые затем будут отражены в сгенерированных классах. Доступны к использованию OneToOne, OneToMany и ManyToMany. Так получилось, что я всегда использовал связь OneToOne, но хотелось бы узнать про остальные типы связей (имеется ввиду не теория, с этим все в порядке, а именно реализация в onPHP).

— Pattern — представляет собой шаблон будущего класса. Особенностей всех шаблонов я, к сожалению, не знаю, однако вот некоторые из них:
  1. StraightMapping — из названия понятно, что это прямой маппинг объекта на одну запись в таблице БД. Это ничто иное, как ActiveRecord М.Фаулера.
  2. EnumerationClass — перечисление. Класс, который не имеет таблицы в БД, а является линейным списком (id => name). Используется для тех данных, которые никогда или практически никогда не меняются (например, месяца, знаки зодиака, и т. д.)
  3. AbstractClass — абстрактный класс. Используется для определения в схеме абстрактных классов, от которых в этой же схеме другие классы будет наследоваться.
Остальные паттерны (DictionaryClass, SpookedClass, SpookedEnumeration, ValueObject и InternalClass) мне пока неведомы, надеюсь знающие расскажут, а я запишу.

— fetch=«lazy» — это указание на то, что при сборке объекта, свойство Author загружать не нужно до обращения к нему. В приведенном примере, если я выгружу объект Book по ID:
$book = Book::dao()->getById(1);
$author = $book->getAuthor();
то для сборки объекта Author будет сделан запрос в БД на получение значений его параметров. При fetch=«cascade» все данные по всем связям выгребаются из БД одним запросом, после чего связанные объекты собираются и доступны для использования без дополнительных запросов.

Это далеко не все, что можно использовать в meta, но для краткого обзора достаточно. Начнем генерировать классы.

Кодогенерация.


В папке meta есть скрипт bin/build.php, который с помощью рядом находящихся классов и превращает схему в классы. Посмотрим как его использовать:
Usage: build.php [options] [project-configuration-file.inc.php] [metaconfiguration.xml]

Meta у нас есть, а вот файла конфигурации — нет. Создадим его.

В корне есть файл global.inc.php.tpl, который является шаблоном для файла конфигурации. Копируем его и изменяем следующим образом:
а) добаляем константу PATH_CLASSES — там, где у вас будут лежать сгенерированные классы.
б) Добавляем в set_include_path():
.PATH_CLASSES.PATH_SEPARATOR
.PATH_CLASSES.'Auto'.PATH_SEPARATOR
.PATH_CLASSES.'Auto'.DIRECTORY_SEPARATOR.'Business'.PATH_SEPARATOR
.PATH_CLASSES.'Auto'.DIRECTORY_SEPARATOR.'DAOs'.PATH_SEPARATOR
.PATH_CLASSES.'Auto'.DIRECTORY_SEPARATOR.'Proto'.PATH_SEPARATOR
.PATH_CLASSES.'Business'.PATH_SEPARATOR
.PATH_CLASSES.'DAOs'.PATH_SEPARATOR
.PATH_CLASSES.'Proto'.PATH_SEPARATOR
в) добавляем параметры соединения с БД:
DBPool::me()->setDefault(
DB::spawn('PgSQL', 'user', 'password', 'localhost', 'dbname')
->setEncoding('utf-8')
);

Затем, запускаем build.php:

$ ./meta/bin/build.php ./global.inc.php ./schema/project.xml 

onPHP-1.1: MetaConfiguration builder.

Known internal classes: Range, DateRange, TimestampRange, IdentifiableObject, IdentifiableTree, NamedObject, NamedTree, that's all.

Building classes:
	Author:
		AutoProtoAuthor (classes/Auto/Proto/AutoProtoAuthor.class.php)
		ProtoAuthor (classes/Proto/ProtoAuthor.class.php)
		AutoAuthor (classes/Auto/Business/AutoAuthor.class.php)
		Author (classes/Business/Author.class.php)
		AutoAuthorDAO (classes/Auto/DAOs/AutoAuthorDAO.class.php)
		AuthorDAO (classes/DAOs/AuthorDAO.class.php)

	Book:
		AutoProtoBook (classes/Auto/Proto/AutoProtoBook.class.php)
		ProtoBook (classes/Proto/ProtoBook.class.php)
		AutoBook (classes/Auto/Business/AutoBook.class.php)
		Book (classes/Business/Book.class.php)
		AutoBookDAO (classes/Auto/DAOs/AutoBookDAO.class.php)
		BookDAO (classes/DAOs/BookDAO.class.php)

Building containers: 

Building DB schema:
		schema.php (classes/Auto/schema.php)

Suggested DB-schema changes: 
table 'authors' not found, skipping.
table 'books' not found, skipping.

Checking for stale files: 

Trying to compile all known classes... done.

Checking sanity of generated files: 

	Author (2/1/
ERROR:  42P01: relation "authors" does not exist - SELECT "authors"."id", "authors"."name" FROM "authors" WHERE ("authors"."id" IS NOT NULL) ORDER BY "authors"."id" LIMIT 1

[exception trace вырезан]

Что произошло: builder взял конфигурационный файл и по схеме создал классы (раздел Building classes), создал файл схемы (раздел Building DB schema), проверил наличие устаревших файлов, проверил синтаксис во всех файлах проекта и, проверяя правильность сгенерированных классов, нашел ошибку, что таблицы-то мы еще не создали. Исправимся. Заходим на базу и выполняем 2 запроса:
CREATE TABLE authors (id serial PRIMARY KEY);
CREATE TABLE books (id serial PRIMARY KEY);

Запускаем еще раз build.php:

$ ./meta/bin/build.php ./global.inc.php ./schema/project.xml 

onPHP-1.1: MetaConfiguration builder.

Known internal classes: Range, DateRange, TimestampRange, IdentifiableObject, IdentifiableTree, NamedObject, NamedTree, that's all.

Building classes:
	Author:
		AutoProtoAuthor 
		AutoAuthor 
		AutoAuthorDAO 

	Book:
		AutoProtoBook 
		AutoBook 
		AutoBookDAO 

Building containers: 

Building DB schema:
		schema.php 

Suggested DB-schema changes: 
ALTER TABLE "authors" ALTER COLUMN "id" TYPE BIGINT;
ALTER TABLE "authors" ADD COLUMN "name" CHARACTER VARYING(50) NOT NULL;

ALTER TABLE "books" ALTER COLUMN "id" TYPE BIGINT;
ALTER TABLE "books" ADD COLUMN "title" CHARACTER VARYING(50) NOT NULL;
ALTER TABLE "books" ADD COLUMN "author_id" BIGINT NOT NULL REFERENCES "authors"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
CREATE INDEX "author_id_idx" ON "books"("author_id");


Checking for stale files: 

Trying to compile all known classes... done.

Checking sanity of generated files: 

	Author (2/1/F/C/H/T), Book (3/1/
ERROR:  42703: column books.title does not exist - SELECT "books"."id", "books"."title", "books"."author_id" FROM "books" WHERE ("books"."id" IS NOT NULL) ORDER BY "books"."id" LIMIT 1

Обратите внимание на Suggested DB-schema changes, выполним эти запросы и снова запустим build.php:

$ ./meta/bin/build.php ./global.inc.php ./schema/project.xml 

onPHP-1.1: MetaConfiguration builder.

Known internal classes: Range, DateRange, TimestampRange, IdentifiableObject, IdentifiableTree, NamedObject, NamedTree, that's all.

Building classes:
	Author:
		AutoProtoAuthor 
		AutoAuthor 
		AutoAuthorDAO 

	Book:
		AutoProtoBook 
		AutoBook 
		AutoBookDAO 

Building containers: 

Building DB schema:
		schema.php 

Suggested DB-schema changes: 

Checking for stale files: 

Trying to compile all known classes... done.

Checking sanity of generated files: 

	Author (2/1/F/C/H/T), Book (3/1/F/C/H/T), done.

Всё, классы сгенерированы. Давайте посмотрим на них. В каталоге, который был указан в константе PATH_CLASSES сейчас находятся 4 подкаталога:
  • Auto: классы, которые содержат не подлежащие изменению классы, которые перегенерируются всякий раз при запуске build.php;
  • Business: бизнес-объекты, которые можно и нужно изменять, дополняя необходимой логикой;
  • DAOs: Data Access Objects, объекты доступа к данным, в которые можно поместить часто используемые выборки;
  • Proto: классы, описывающие прототипы объектов, их свойства.

Пример использования созданных классов.


Код:
require "global.inc.php";

DBPool::me()->getLink()->begin();

$author = Author::create()
->setName('Martin Fowler');
Author::dao()->add($author);

$book = Book::create()
->setTitle('Patterns of Enterprise Application Architecture')
->setAuthor($author);
Book::dao()->add($book);

DBPool::me()->getLink()->commit();

Результат:

dbname=> select * from authors;
 id |     name      
----+---------------
  1 | Martin Fowler
(1 row)

dbname=> select * from books;
 id |                      title                      | author_id 
----+-------------------------------------------------+-----------
  1 | Patterns of Enterprise Application Architecture |         1
(1 row)


На этом пока все, в следующий раз планирую написать про MVC в onPHP и полноценный пример приложения, а также осветить вопросы, которые будут заданы. Надеюсь тема onPHP найдет позитивный и конструктивный отклик в виде комментариев к этой заметке.
Спасибо за внимание.

UPD:
Ссылки:
— onphp.org
GIT
блог Дениса Габайдулина, одного из создателей onPHP
Tags:
Hubs:
+35
Comments 84
Comments Comments 84

Articles