BlackFox — PHP фреймворк для веб-сайтов и приложений

Reuniko

Новичок
Здравствуйте,
представляю вашему вниманию BlackFox.

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

---
Вот например для работы с базой данных в Laravel используется Eloquent ORM (паттерн Active Table) с конструктором запросов. Конструктор запросов является ни чем иным как полной копией языка запросов SQL, реализованным в виде обертки на PHP. Да, он позволяет "легко и быстро" мигрировать с одной базы данных на другую, но как часто вы это делаете? А для того чтобы добавить колонку в таблицу, необходимо создать миграцию. Для того чтобы удалить - миграция. Для того чтобы переименовать или изменить - снова миграция. И все эти миграции необходимо держать в уме при релизах.

BlackFox использует иной подход. Отнаследовав от класса SCRUD вы создаете свою таблицу, описывая ее поля в виде структурного массива. Когда требуется добавить\изменить\удалить — просто запускается метод Synchronize(), который ищет разницу между вашим описанием и реальными колонками в базе данных и эту разницу нивелирует. А метод поиска информации Search() так вообще конфетка, он принимает на вход массив фильтров, переданных напрямую из формы, сам автоматически эскейпает все принимаемые данные, да еще и выдает отэскейпанные данные на выходе, чтобы ваша голова не болела при составлении очередного отображения.

---
Или вот например в Yii контролеры и отображения разнесены по разным директориям, как будто некоторые отображения могут быть использованы более чем одним контроллером. А действие контроллера вынужденно возвращать готовый отрендеренный html, что не позволяет элегантно переиспользовать его там где нужен точно такой же ответ, но в формате json, xml или ajax.

BlackFox использует иной подход. Контроллеры и отображения объединены в единую структуру, которой управляет класс-наследник от Unit. Все публичные методы этого класса являются действиями и возвращают массив данных, подключаемых к отображению. А пользователю предоставляется возможность самому решать в каком формате он хочет получить ответ. Но самое вкусное тут это возможность наследования контроллеров вместе с отображением: если у отнаследованного контроллера отсутствует отображение, то будет подключено отображение родителя (или родителя родителя...).

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

В BlackFox вы регистрируете в конфигурации все папки, являющиеся виртуальными корнями (http://blackfox.reuniko.com/learn/basics/root.php), а затем создаете в них привычную структуру из папок и файлов, как в старом добром нативном PHP. А если возникает потребность в ловле запросов на несуществующие файлы\папки, то создаете в соответствующем разделе файл .router.php, на который такие запросы перенаправляются движком. В файле .router.php можно описать роутинг любой сложности.

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

Благодарю за внимание =)

 
Последнее редактирование:

c0dex

web.dev 2002-...
Команда форума
Партнер клуба
Открыл config.php, увидел global $CONFIG;, закрыл.
 

fixxxer

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

Подсказываю.
1) ORM нужны не для того, чтобы переходить между базами. Для чего нужны ORM, можно узнать, посмотрев в интернете, как расшифровывается эта аббревиатура. А объекты на РСУБД далеко не всегда мапятся 1:1.
2) View и Model потому и разделены, что они практически никогда не мапятся 1:1.
3) Посмотри Symfony 4 и Cycle ORM, изучи как следует - это примеры сделанного хорошо, хотя с таким подходом это может быть и неочевидно.
 

Reuniko

Новичок
Ну то есть посмотрел на два популярных (но не самых лучших) фреймворка, нашел, что там не очень (а там многое не очень, но совсем другое),
Я не стал перечислять в этом посте все движки, с которыми мне довелось познакомиться.
Какой "самый лучший" движок на ваш взгляд?

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

1) ORM нужны не для того, чтобы переходить между базами. Для чего нужны ORM, можно узнать, посмотрев в интернете, как расшифровывается эта аббревиатура. А объекты на РСУБД далеко не всегда мапятся 1:1.
Я представляю себе что такое ORM. Чего я не представляю себе это зачем нужна такая реализация ORM, которая по сути повторяет весь синтаксический сахар SQL на PHP, единственным преимуществом которого я вижу независимость от конкретной БД. По мне так SQL довольно неплохой язык, хорошо читаемый и достаточно гибкий чтобы использовать его вместо подобных ORM.

2) View и Model потому и разделены, что они практически никогда не мапятся 1:1.
Я говорил про Controller + View.

3) Посмотри Symfony 4 и Cycle ORM, изучи как следует - это примеры сделанного хорошо, хотя с таким подходом это может быть и неочевидно.
Cycle ORM:
- выносит в php-комментарии описание типов колонок в базе данных, в результате ему приходится использовать нелегкие рефлекшены для чтения этих комментов и дублицирования оных в свои внутренние состояния
- использует адаптер, в результате для доступа к одной таблице используются два объекта
- на каждую строку в таблице генерирует отдельный объект, для которого необходимо описывать подобный мусор:
PHP:
    public function getId(): int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }
Я не говорю что Cycle ORM это что-то плохое, я говорю что можно делать все то-же самое намного проще и приятнее и предлагаю свой вариант.

BlackFox SCRUD:
- описывает колонки в виде структурного массива, что делает это описание легко и быстро доступным
- использует наследование, в результате для доступа к одной таблице используются один объект, который к тому же может быть неизменяемым и глобальным
- на каждую строку в таблице возвращает ассоциативный массив, а не объект, что уменьшает количество используемой памяти и упрощает код

Для сравнения:
примеры кода на Cycle ORM
примеры кода на BlackFox SCRUD
 

fixxxer

К.О.
Партнер клуба
Изобретать велосипеды нужно и полезно
Согласен, только надо понимать, что это делается для саморазвития, твой велосипед всегда соответствует твоему текущему уровню, и через пару лет (если будешь развиваться) поймешь, какая это фигня. Это я по своему опыту говорю, я много велосипедов писал лет 10 назад, в том числе очень похожих.

Какой "самый лучший" движок на ваш взгляд?
Тот, который не мешает программировать в предпочитаемом стиле, не навязывает свои подходы.
Вообще от фреймворка кроме Dependency Injection и Http Kernel ничего и не надо по большому счету.

Я говорил про Controller + View.
А в контроллере вообще не должно быть больше 3-5 строк кода.

зачем нужна такая реализация ORM, которая по сути повторяет весь синтаксический сахар SQL
Ты путаешь ORM и Query Builder. Это две разные вещи. Query Builder удобны для динамического построения запросов (например, если по условию надо добавить JOIN и WHERE, с голыми строками получается жуть, особенно если IN () по строкам). Никто не говорит, что надо их использовать всегда.

Главная задача ORM - положить ("сериализовать") объект в базу данных и достать оттуда "как было".

Для read models ORM вообще ни к чему, а вот Query Builder часто удобен. (Почитай про CQRS, если не ясна разница между Write и Read models).
Заодно почитай определение, что такое модель (подсказка - от слова "моделировать", к таблицам в базах данных не имеет отношения).

Cycle ORM:
- выносит в php-комментарии описание типов колонок в базе данных, в результате ему приходится использовать нелегкие рефлекшены для чтения этих комментов и дублицирования оных в свои внутренние состояния
Нет, это опциональный пакет, схему маппинга можно описывать и отдельно, простым декларативным способом в виде PHP-кода.
- использует адаптер, в результате для доступа к одной таблице используются два объекта
Да хоть двадцать два. Какая разница? Реальные бенчмарки делал, или экономишь на спичках?
- на каждую строку в таблице генерирует отдельный объект, для которого необходимо описывать подобный мусор
"Мусор" делать надо только если у тебя геттерно-сеттерный подход, то есть антипаттерн Anemic Model. https://martinfowler.com/bliki/AnemicDomainModel.html
В нормально спроектированной модели геттеры и сеттеры вообще не нужны.
 

Yoskaldyr

"Спамер"
Партнер клуба
Поддержу что Cycle ORM на данный момент самый адекватный орм сейчас
 

Reuniko

Новичок
Тот, который не мешает программировать в предпочитаемом стиле, не навязывает свои подходы.
Вообще от фреймворка кроме Dependency Injection и Http Kernel ничего и не надо по большому счету.
BlackFox не мешает программировать в предпочитаемом стиле и не навязывает свои подходы. Туда можно без всяких проблем подключить любимую ORM и любые другие либы. Там даже можно отнаследовать Engine (местный Http Kernel) и переопределить любые методы на свой вкус.
А вообще, я ожидал конкретики. Я бы с удовольствием почитал доку к фреймворку, который на ваш взгляд "лучший".

Ты путаешь ORM и Query Builder. Это две разные вещи. Query Builder удобны для динамического построения запросов (например, если по условию надо добавить JOIN и WHERE, с голыми строками получается жуть, особенно если IN () по строкам). Никто не говорит, что надо их использовать всегда.

Главная задача ORM - положить ("сериализовать") объект в базу данных и достать оттуда "как было".

Для read models ORM вообще ни к чему, а вот Query Builder часто удобен. (Почитай про CQRS, если не ясна разница между Write и Read models).
Заодно почитай определение, что такое модель (подсказка - от слова "моделировать", к таблицам в базах данных не имеет отношения).
Очень может быть я чего-то путаю или недопонимаю в ORM. Может быть поэтому я и стал пилить свой велосипед в совершенно иной парадигме - CRUD. Может даже быть что не имеет особого смысла их между собой сравнивать, все таки подходы в корне различны.

Нет, это опциональный пакет, схему маппинга можно описывать и отдельно, простым декларативным способом в виде PHP-кода.
Не могу найти примеров декларативного объявления в виде php-кода. Или вы про yml?

Да хоть двадцать два. Какая разница? Реальные бенчмарки делал, или экономишь на спичках?
Я экономлю самый важный ресурс: оперативную память программиста. Количество объектов, которые необходимо держать в голове matters. Чем их меньше, тем проще и качественнее код, тем легче этот код поддерживать и работать в команде.

Поддержу что Cycle ORM на данный момент самый адекватный орм сейчас
Очень может быть :)
Но я все еще не вижу чем ORM'ы удобнее потабличного CRUD'a, зато вижу море недостатков, основные из них:
- повторение сахара SQL
- большая сложность в изучении и использовании

На вкус и цвет фломастеры разные, конечно. Поэтому у меня нет цели продать доказать вам что мои фломастеры вкуснее.

Моя цель — найти людей, которым мои фломастеры придутся по вкусу.
А чтобы понять вкусный фломастер или нет, нужно мыслить чуточку шире шаблонов "global это плохо" или "контроллер должен состоять из максимум 5 строк" и хотя бы пролистать доку по диагонали, чтобы составить базовое представление о том что в движке есть, а чего не хватает.

Добавил параграф "Примеры использования" в документацию SCRUD.
 

fixxxer

К.О.
Партнер клуба
Да мне не надо ничего читать, я сам это все проходил лет 10 назад, и делал похожее.

Я экономлю самый важный ресурс: оперативную память программиста
Декомпозиция как раз позволяет ее экономить: реализация выносится в абстракцию, и достаточно знать только о контракте.
Конечно, при условии, что программист понимает ООП. Кому-то проще с портянками процедурного кода работать.

Не могу найти примеров декларативного объявления в виде php-кода

Я бы с удовольствием почитал доку к фреймворку, который на ваш взгляд "лучший".
Идеала нет, но sf4 самый вменяемый на сегодня (если разобраться и делать не копипастом из мануала, а по-своему). Хотя мне там тоже не все нравится.
Очень может быть я чего-то путаю или недопонимаю в ORM
Да нет, не путаешь, просто твое представление о вещах сформировано Rails-подобными фреймворками, которые популяризовали антипаттерны (настолько, что люди считают, что это и есть MVC и ООП, и критикуют их в целом - хотя это проблемы не MVC и ООП, а именно Rails и последователей типа Laravel и Yii). Есть принципиально другой подход, который и есть настоящее ООП. Он сложнее для понимания, и его надо изучать и осваивать, но в итоге он позволяет управлять сложностью и предотвращать ситуацию, когда проект с ростом сложности превращается в месиво.

Это долго объяснять, может, найду время на выходных. Если есть интерес, советую почитать вот эти книги (где скачать, сам найдешь):
 

fixxxer

К.О.
Партнер клуба
Ну еще вопрос какие задачи стоят.
Если сайтики клепать, то вообще пофигу на чем.
 

Adelf

Administrator
Команда форума
В конце месяца целый доклад буду делать о том, почему не надо мыслить CRUD-оподобно. Любой проект чуть сложнее бложика и везде выплывают сущности, которые ведут себя не CRUD :) Правда это сложно обьяснить.

@Reuniko все через это проходили. И ты пройдешь и пойдешь дальше. Если повезет :) Но придется набить пару шишек. Без этого обычно не обходится.
 

флоппик

promotor fidei
Команда форума
Партнер клуба
Чет вы добрые стали, старые уже наверное. Нет бы спросить, почему мамка в летний лагерь не отправила!
 

Reuniko

Новичок
Декомпозиция как раз позволяет ее экономить: реализация выносится в абстракцию, и достаточно знать только о контракте.
Конечно, при условии, что программист понимает ООП. Кому-то проще с портянками процедурного кода работать.
Декомпозиция декомпозиции рознь. Можно декомпозировать и абстрагировать до бесконечности, можно вообще юзать шестые нормальные формы в базе данных. BlackFox SCRUD декомпозирует минимально, что экономит ресурс программиста сильнее, чем "средняя" декомпозиция, используемая в ORM.

Нашел такой пример "декларативного" объявления в Cycle-orm:
PHP:
class User implements AuthorInterface
{
    public static function define(): Entity
    {
        $entity = new Entity();
        $entity->setRole('user');
        $entity->setClass(self::class);;
        $entity->getFields()->set(
            'id', (new Field())->setType('primary')->setColumn('id')->setPrimary(true)
        );
        $entity->getFields()->set(
            'name',
            (new Field())->setType('string(32)')->setColumn('user_name')
        );
        $entity->getFields()->set(
            'active',
            (new Field())->setType('bool')->setColumn('active')
        );
        $entity->getFields()->set(
            'balance',
            (new Field())->setType('float')->setColumn('balance')
        );
        $entity->getFields()->set(
            'created_at',
            (new Field())->setType('datetime')->setColumn('created_at')
        );
        $entity->getRelations()->set(
            'plain',
            (new Relation())->setTarget('plain')->setType('hasOne')
        );
        return $entity;
    }
}
Данный подход на вскидку обладает следующими "преимуществами":
- возможность вызвать setType без setColumn без setPrimary, да и к варианту вызова setPrimary(false) у меня есть некоторые вопросы
- код колонки почему-то знает Entity, но не знает Field, видимо чтобы можно было продублировать один и тот же Field в разные колонки Entity
Итого имеем 4+ типов объектов: User, Entity, Field и в последующем ORM для доступа к изменению\добавлению элементов.

Та же самая таблица на BlackFox SCRUD выглядела бы так:
PHP:
<?
namespace Example;
class User extends \System\SCRUD {
    public $structure = [
        'ID'         => self::ID,
        'NAME'       => [
            'TYPE'   => 'STRING',
            'NAME'   => 'Name',
            'LENGTH' => 32,
        ],
        'ACTIVE'     => [
            'TYPE' => 'BOOL',
            'NAME' => 'Active',
        ],
        'BALANCE'    => [
            'TYPE' => 'FLOAT',
            'NAME' => 'Balance',
        ],
        'CREATED_AT' => [
            'TYPE' => 'DATETIME',
            'NAME' => 'Created at',
        ],
        'PLAIN'      => [
            'TYPE' => 'OUTER',
            'NAME' => 'Plain',
            'LINK' => 'Plain', // this is the link to the class \Example\Plain
        ],
    ];

    public function Create($fields) {
        $fields['CREATED_AT'] = time();
        return parent::Create($fields);
    }
}
В последующем, по умолчанию, доступ к полям этого пользователя осуществлялся бы с созданием одного глобального неизменяемого инстанса, с сохранением возможности создавать локальный инстанс, если он для чего-то нужен:
PHP:
<?
// method I() returns global instance
$user_id = \Example\User::I()->Create([
    'NAME' => 'Reuniko',
    'PLAIN' => 34,
]);

// 'new' creates local instance
$User = new \Example\User;
$user_id = $User->Create([
    'NAME' => 'Reuniko',
    'PLAIN' => 34,
]);
Идеала нет, но sf4 самый вменяемый на сегодня (если разобраться и делать не копипастом из мануала, а по-своему). Хотя мне там тоже не все нравится.
У меня сложилось впечатление что вы не из того типа людей, которые стремятся к идеалу. Иначе бы вы уже написали свой фреймворк идеальный хотя бы для себя, тем более что там и нужно то всего лишь "HTTP handler + Dependency Injector", работы на неделю максимум.

Да нет, не путаешь, просто твое представление о вещах сформировано Rails-подобными фреймворками, которые популяризовали антипаттерны (настолько, что люди считают, что это и есть MVC и ООП, и критикуют их в целом - хотя это проблемы не MVC и ООП, а именно Rails и последователей типа Laravel и Yii).
Очень забавно читать представление о моем представлении в вашей голове. К истине оно не имеет отношения, увы.

Есть принципиально другой подход, который и есть настоящее ООП. Он сложнее для понимания, и его надо изучать и осваивать, но в итоге он позволяет управлять сложностью и предотвращать ситуацию, когда проект с ростом сложности превращается в месиво.
Ваше разделение ООП на настоящее и не настоящее понятно только вам. Мне не понятно зачем его вообще разделять.
Я до сих пор помню тот момент в моей жизни, когда в моей голове сложилось что "структура данных + методы их обрабатывающие = объект", для меня это стало настоящей эврикой, сразу же возник целый веер возможностей. Резать этот веер на куски только потому что какие то "мудрые" дяденьки в каких то статьях и книгах называют одни подходы "паттернами", а другие -- "антипатернами"... ну это не по мне. По мне это изучать их "паттерны" в боевых условиях, сравнивать и выбирать лучшее для себя.

Что касается моего "не настоящего" ООП, последние 10 лет я только и делал что управлял сложностью и предотвращал ситуации превращения сложных проектов в месиво. Было бы клево, если бы я мог с вами поделиться инфой о том бизнес-процессы какой сложности хотят себе заказчики из Газпром-Нефти на свои корпоративные порталы, и как приходится их удовлетворять в условиях практически нулевой обратной связи. В результате получился BlackFox, фреймворк, на котором в данный крутятся бизнес-процессы на много порядков сложнее, чем в любой коробочной CRM.
 
Последнее редактирование:

Reuniko

Новичок
В конце месяца целый доклад буду делать о том, почему не надо мыслить CRUD-оподобно. Любой проект чуть сложнее бложика и везде выплывают сущности, которые ведут себя не CRUD :) Правда это сложно обьяснить.

@Reuniko все через это проходили. И ты пройдешь и пойдешь дальше. Если повезет :) Но придется набить пару шишек. Без этого обычно не обходится.
Корпортал "Идеи" позволяет сотрудникам фирмы подать идею. Идея попадает на модерацию к админам в общий пул. После модерации идея идет в статус "Этап 1", где голосуют за\против приглашенные эксперты, связанные по месту работы сотрудника (мест работ четыре уровня: от города до установки). Если 50%+ голосуют "за", идея попадает на "Этап 2", где те же самые эксперты + дополнительно приглашенные эксперты выставляют веса по различным параметрам (от экономической себестоимости до актуальности), которые в результате суммируются и капают на баланс пользователя, эту идею подавшего. Ну и дальше несколько этапов реализации с назначением ответственных и проверяющих.

Посложнее бложика? Все реализовано на CRUD. Голова не болит. Команда программистов счастлива. "Сущности которые ведут себя не CRUD" не найдены.

P.S. Доклад почитаю =)
 

fixxxer

К.О.
Партнер клуба
Как-то так:
PHP:
$user = User::registerByEmail($userRepository->allocateId(), $email, $password, 'Петя');
$userRepository->persist($user);
$eventDispatcher->dispatchEvents($user->popEvents());
$user = $userRepository->findById(1)
$user->rename('Вася');
$userRepository->persist($user);
$eventDispatcher->dispatchEvents($user->popEvents());
$userRepository->delete($user);
 

Reuniko

Новичок
Как-то так:
PHP:
$user = User::registerByEmail($userRepository->allocateId(), $email, $password, 'Петя');
Я бы запихнул allocateId внутрь registerByEmail (или даже еще глубже), потому что возможность регистрировать пользователя со случайным\конкретным айдишником мне не нужна.

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

fixxxer

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

Давай добавим логику как во Вконтакте "имя можно изменить только 1 раз, а все последующие изменения идут на модерацию", и вот тогда уже поговорим про crud. :)
 
Сверху