Hello!
The next suicidal article from me on the topic of Bitrix, I hope this time the community will be more lenient, because here everything is in fact, with the code, the schema, no holivar and everything is fair.
In this article I will consider an alternative to BitrixFramework, which is designed to make life easier for a developer and somehow influence the development of the CMS Bitrix in the right direction.
')
Action for haters: if write a comment with normal criticism and on the topic + I will personally send to karma ;-). Here you have Wolfich for starters, everything is interesting inside ...

To begin with, dot all the 'i'
All that is written below is my personal opinion and can be quietly thrown in slippers, but I am sure that I am right:
- “Bitrix has a million lines of shit” - yes, no doubt. The whole problem is that Bitrix supports backward compatibility (supposedly everything will work fine if you update version 12.0 to 16.5). Why do they do it, I do not know (I think no one knows). If we talk about the source code of standard components (2K lines of code for outputting information block elements - in the order of things), here the guys decided to facilitate the work of end users and provided for everything that could be foreseen (and not a fact), and all this is very rare when needed. Well, in dogonku, at a recent seminar, the developer Bitrix reported his attitude to the PSR: "Well, this PSR, I read it, some guys gathered and wrote some garbage" (not an exact quote). So the code will always smell.
- “Bitrix has a terrible structure” - not really. Bitrix is ​​based on files, and the understanding of MVC is different from the generally accepted one, and for many, “not MVC” = “horror-horror — which structure”. So this is a very controversial issue. And with the Bitrix structure you can live.
- “BitrixFramework will never evolve” - this is my opinion and this is why: with each release, Bitrix only modifies the “Store” module, make some edits, but they are all aimed at the store. On the rest, they frankly do not care. The development of BF will begin when they abandon backward compatibility and begin to deal not only with the “Shop” module.
Meet Juggernaut!
Surely, many are familiar with this character (from Marvel, not from Dota), which is “impossible to stop”. A slightly pompous name actually reflects the essence of this project:
it doesn’t matter how much Bitrix is ​​developing, what innovations it introduces and what it does, anyway, the library will live and flourish.Bitrix is aimed at users.
Juggernaut targets developers.
Why is this necessary?
Because it is necessary! Everything is actually very logical:
- Beatrix is ​​developing a new core (good), but they don’t want to document at all (bad);
- Bitrix is ​​developing a new functional (good), but only for the store (bad);
- Bitrix is ​​developing new components (good), but from their code blood from the eyes (bad);
- Bitrix patented the "new" technology "Composite site" (bad).
From version 14, Bitrix needed to simply end up supporting the old core and focus on the new one, but no, “take care of customers.” Rave. This is the same if Yii2 supported and back combined Yii1.
Since Beatrix does not make any progress, the community will do it (instead of whining, writing to the “Idea” service, and somehow unscrewing using standard components).
Bitrix scolded, now you can proceed to the review of Juggernaut. Then the review of the components of the library and a brief description of their use will begin.
Components
Components are the bricks from which to build the site on Bitrix. Components are conventionally divided into 2 categories: widgets and routers (in Bitrix notation: “normal” and “integrated”).
Widget
A widget is a component that stupidly does one elementary task (displays a form, a list, information). The widget receives the input data and in some way converts them. He should do nothing more. Widgets do not control routing, but can use it.
The default execution order of the component:

In order:
- init - initializes the initial data. Converts input parameters ($ arParams) to class properties;
- onBefore - checks the possibility of an action;
- isCachedTemplate - a flag that determines if there is a cached copy. If there is, it outputs cache data; if it does not, it generates it (in the code, it looks a little different, the diagram indicates this for simplicity);
- initResult - forms data for presentation ($ arResult);
- run - the function of direct execution of the widget. It defines what needs to be done with the data ($ arResult);
- onBeforeRender - checks the possibility of outputting a template and performs any transformations (analogue result_modifier.php, although you can use it);
- render - direct output of the component template;
- onAfter - performing the action after the widget has been completed (analogous to component_epilog.php).
Most often, it is enough to override the
initResult method and
distribute the component template.
Below is an example of a component class (class.php), which displays a list of information block elements. At the input, it receives an array of parameters ($ params), which are used to filter and sort the data.
Code<?php namespace Widget\Iblock\Element\List_; use Jugger\Db\Orm\Ib\IblockElement; use Jugger\Component\WidgetComponent; class Component extends WidgetComponent { public $params = []; protected function init() { parent::init(); $this->isCachingTemplate = true; } protected function initResult() { $this->arResult['elements'] = IblockElement::getList($this->params); } }
Router
The task of the router is to collect widgets together. A router is a controller that, based on a user request (REQUEST_URI), triggers the corresponding action. An action can be either a page with information (including widgets) or it can contain some kind of logic.
The default execution order of the component:

In order:
- init - initializes the initial data. Converts input parameters ($ arParams) to class properties;
- initUrlManager - fills in UrlManager route data (aliases). This action is necessary to perform routing on the action and the further generation of URL addresses;
- parseRequest — parses the UrlManager request and determines which action is requested by the user;
- existBeforeAction - check onBefore personal handler availability. If there is a 'index' action and there is a 'onBeforeIndex' method, then it will be called exactly, otherwise the common 'onBefore' will be called;
- onBefore - checks the possibility of an action;
- run - function of direct execution of components. It defines what needs to be done with the data ($ arResult);
- existMethodAction - checking for an action handler. If the requested action is 'index' and there is a method 'actionIndex', then this method will be called, otherwise the router will try to display a view with the name 'index';
- onBeforeRender - checks whether the template can be output and performs any transformations (the name of the action is passed in the parameters, so you can customize the personal check);
- render - direct output of the component template;
- onAfter - performing the action after the widget has been completed (analogous to component_epilog.php). It works similarly with the 'onBefore' method: if the 'onAfterIndex' method exists for the 'index' action, then it will be called, otherwise the general 'onAfter'.
Below is an example of the component that the directory implements:
- list of items
- list of sections
- detailed element card.
Code <?php namespace Widget\Iblock\Element\Catalog; use Jugger\Db\Orm\Ib\IblockElement; use Jugger\Db\Orm\Ib\IblockSection; use Jugger\Component\RouteComponent; class Component extends RouteComponent { public $iblockId; protected function getAliases() { return [ "sectionList" => "index.php", "elementList" => "#SECTION_CODE#/", "elementView" => "#SECTION_CODE#/#ELEMENT_CODE#/" ]; } protected function onBefore($action) { if (!$this->iblockId) { throw new \Exception(" 'iblockId' ". get_called_class()); } return parent::onBefore($action); } protected function getSection($sectionCode) { return IblockSection::getRow([ "filter" => [ "IBLOCK_ID" => $this->iblockId, "CODE" => $sectionCode ], ]); } public function actionSectionList() { $sectionList = IblockSection::getListByField( "=IBLOCK_ID", $this->iblockId, [ "order" => ["SORT" => "ASC"] ] ); $this->arResult['sectionList'] = $sectionList; $this->render('list'); } public function actionElementList($sectionCode) { $section = $this->getSection($sectionCode); if (!$section) { $this->error404(); }
Autoload classes
I will not speak much on this issue, because it’s already clear that this is a very necessary thing, I’ll just describe how it works.
As implemented in Juggernaut:
In the “lib” folder you should observe the following structure: the names of the class files are identical to the namespace names, not including the extension and upper namespace. For example, the file "... / modules / Iblock / lib / Property / Table.php" will correspond to the class "Iblock \ Property \ Table".
Calling "includeModule" is no longer necessary, since if necessary, all classes will be loaded automatically from the necessary directories.
If the module directory is different from the namespace name, or in any other situation, you can manually set the matching namespace and directory:
In Bitrix, autoloading is also implemented, but it forms the path a little differently:
The class “Olof \ Catalog \ Tools \ File” is translated as “/Olof.Catalog/lib/Tools/File.php”.
If you need the class “Olof \ Catalog” - then excuse me, point out its presence with your hands (see below). The directory of the module should be with the delimiter “.” Otherwise walk through the forest. The directory “olof.catalog.iblock” is incorrect.
The gentlemen from Bitrix actually did a normal thing: they took care of specifying the vendor in the module name, but I think this is an unnecessary condition for naming the directory.Autoloading implicitly reacts to classes of the type “ElementTable” by deleting the postfix, translating them into files “element.php”. Actually, because of this, you cannot create a class named Table.
It is also impossible to load classes from modules that are not currently connected (includeModule).
Consider an example of how the Bitrix version works: we have the “olof.iblock” module and the corresponding include.php file:
namespace Olof\Iblock; use Bitrix\Main\Loader;
Too many implicit conditions and conditions in my opinion. And no one knows what stupidity Beatrix will come up with tomorrow. And they should come up with an indication of the directory for the namespace prefix (as in PSR-4) and then it will be cool. And while there is a Juggernaut ;-)
ActiveRecord
For the convenience of working with entities, and in particular with information blocks, the ActiveRecord template is implemented. At the moment, AR is based (in fact it is a superstructure) on Bitrix DataMappers, in the future it is planned to complete the transfer to an independent ORM / DAO.
Below is an example of working with information blocks via AR, and almost all currently available methods are covered.
Code use Jugger\Db\Orm\Ib\Iblock; use Jugger\Db\Orm\Ib\IblockSection; use Jugger\Db\Orm\Ib\IblockElement; $iblock = Iblock::getByPrimary(1); $iblock = Iblock::getRowByField("=ID", 1); $iblock = Iblock::getRow([ "filter" => [ "=ID" => 1, ], ]); $iblock = new Iblock($iblock); $iblock->NAME; $iblock->IBLOCK_TYPE_ID; $iblock->getElements(); $iblock->getSections(); $sectionList = $iblock->getSections(); $sectionList = $iblock->getSections([ "order" => [ "NAME" => "ASC", ], ]); $sectionList = IblockSection::getListByField("=IBLOCK_ID", $iblock->ID); $sectionList = IblockSection::getList([ "filter" => [ "=IBLOCK_ID" => $iblock->ID, ], ]); $section = new IblockSection($sectionList->fetch()); $section->getChilds();
The methods:
getPrimary ,
getRow ,
getRowByField ,
getList ,
getListByField are identical for all ActiveRecord.
The AR functionality is currently quite poor (for example, there is no cross-table lookup), but since they are a wrapper over standard functions, Bitrix Buns can be used in the getList and getRow methods. After creating / borrowing a normal DAO, this moment will be finished.
Hermitage
The strength of Bitrix, and I think many will agree, is its user interface, aka Hermitage. It is very convenient and flexible.
Below is an example of working with the Hermitage:
Code use Jugger\Db\Orm\Ib\IblockSection; use Jugger\Db\Orm\Ib\IblockElement; use Jugger\Ui\Hermitage; use Jugger\Ui\Hermitage\Icon; use Jugger\Context\UrlManager\Iblock; $element = IblockSection::getByPrimary(1); Hermitage::addButtonEditIblockElement($this, $element); Hermitage::addButtonDeleteIblockElement($this, $element); $section = IblockSection::getByPrimary(1); Hermitage::addButtonEditIblockSection($this, $section); Hermitage::addButtonDeleteIblockSection($this, $section); Hermitage::addButton( $component, Iblock::getElementCreateUrl(1), " ", [ "ICON" => Icon::TOOLBAR_CREATE, ] ); Hermitage::addPanelButton("#", "", [ "ICON" => Icon::PANEL_TRANSLATE, ]);
So praised and wrote so little)) In fact, this is enough for user interaction. There is a lot to do with regards to the administrative interface, but this is no longer the Hermitage, and this is all in plans.
Security
In Bitrix, as far as I know (and in this matter, I will not hide, I didn’t pick too much), with the security of the site (specifically in the code) it’s generally sad (only protection from SI). In the future, this section will contain tools to protect against various attacks and malicious actions (XSS, random data generation, various crypto functions, form validation, work with passwords, ...). At the moment, only the tools for protection against CSRF are implemented:
use Jugger\Security\Csrf; if (Csrf::validateTokenByPost()) {
After each check (successful or unsuccessful) - the token is deleted from the session, thus you can check the token only once.
Urlmanager
Routing to Bitrix would not say at the height, so this area is affected in Juggernaut. This class allows you to dynamically create and use URL routes (used in component routers).
Consider an example of parsing and generating a URL:
Code use Jugger\Context\UrlManager; UrlManager::setBaseUrl("/catalog"); UrlManager::addAlias("sectionList", "index.php"); UrlManager::addAlias("elementList", "#SECTION_CODE#/"); UrlManager::addAlias("elementView", "#SECTION_CODE#/#ELEMENT_CODE#/"); $alias = UrlManager::parseRequest(); $folder404 = "/catalog"; $arUrlTemplates = [ "sectionList" => "index.php", "elementList" => "#SECTION_CODE#/", "elementView" => "#SECTION_CODE#/#ELEMENT_CODE#/", ]; $arVariables = [];
In the future, it is also planned to tie in to
urlRewrite.php .
Developments
This class is simply a wrapper over D7 functions, with more convenient use.
use Jugger\Helper\Event; Event::on(" ", function(){
What's next?
Plans for the near future:
- normal QueryBuilder
- normal caching
- normal AssetsManager
- a set of components for creating and working with the administrative interface
- normal routing (tied to HttpException)
Conclusion
A lot of things conceived, a lot of things not done. The library develops as I need it, so it is very dependent on current orders (which are often the same type) and free time.
As I said at the beginning, the project will develop no matter what, only the speed of development depends on the number of its authors and stakeholders. So the choice is yours:
- whining, waiting and adjusting to Bitrix (and the development of BitrixFramework is clearly not a priority);
- take things in hand and help develop juggernaut.
Any interested philanthropist can help (otherwise there is no way), for this you need:
- share an idea
- refactor what is
- do something with your pens
The project is on GitHub, so anyone can edit, add, comment and ask.
Thanks for attention! Constructive criticism is very welcome :-)
PS comments like "yes in FIG Bitrix" a huge request not to write. I am aware of how people are related to this system, and this project is just aimed at refining it. Therefore, if you think that “it is better and easier to do a project on any framework” - then I know this and I am very happy for you, therefore, leave your opinion with you. Thank!
Repository:
github.com/irpsv/juggernaut.bitrix_releaseMarketplace: coming soon