📜 ⬆️ ⬇️

BitrixFramework: take everything into our own hands

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:

  1. “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.
  2. “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.
  3. “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:


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:


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 { /* *    'init', *    $arParams     , *   : $this->params = $arParams['params'] */ 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:


Below is an example of the component that the directory implements:

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 { /* * ID ,   */ public $iblockId; /* *  ,            *  ,        'aliases' */ 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'); } /** *     *  $sectionCode    URL */ public function actionElementList($sectionCode) { $section = $this->getSection($sectionCode); if (!$section) { $this->error404(); } // $this->arResult['section'] = $section; $this->arResult['elementList'] = $section->getElements(); $this->render('section'); } /** *    *      ,       'aliases' */ public function actionElementView($sectionCode, $elementCode) { $section = $this->getSection($sectionCode); if (!$section) { $this->error404(); } // $element = IblockElement::getRow([ "filter" => [ "IBLOCK_ID" => $this->iblockId, "IBLOCK_SECTION_ID" => $section->ID, "CODE" => $elementCode, ], ]); if (!$element) { $this->error404(); } $this->arResult['element'] = $element; $this->arResult['section'] = $section; $this->render('view'); } } 



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:

 //  "Jugger\D7\Iblock"    "./lib/D7/Iblock.php" –      \Jugger\Psr\Psr4\Autoloader::addNamespace('Jugger', __DIR__.'/lib'); //  "Jugger\D7\Iblock"    "./classes/Iblock.php" \Jugger\Psr\Psr4\Autoloader::addNamespace('Jugger\D7', __DIR__.'/classes'); 


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; //   Loader::includeModule("Olof.Iblock"); //      Api Loader::registerAutoLoadClasses("Olof.Iblock", [ "\Olof\Classes\Api" => ". /modules/Olof.Iblock/classes/api.php", ]); //    : // Olof\Iblock\Element -> ./modules/Olof.Iblock/lib/Element.php // Olof\Classes\Api -> ./modules/Olof.Iblock/classes/api.php // Olof\Classes\Help -> ./modules/Olof.Classes/lib/Help.php 


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(); //   1-   $section->getChilds(2); //    2-   ( ,    ) $section->getChilds(0); //    $section->getIblock(); //   /* *    */ $elementList = $section->getElements(); while($element = $elementList->fetch()) { /* *    AR      */ $element = new IblockElement($element); $element->getProperties(); //   /* *    */ $elementProperty = $element->getProperty(1); /* *   (  ) */ $value = $elementProperty->VALUE; $value = $elementProperty->getValue(); /* *       , *           */ $elementProperty->getValueRaw(); //    $elementProperty->getValueEnum(); // IblockPropertyEnum -    (L) $elementProperty->getValueFile(); // CFile::GetFileArray $elementProperty->getValueHtml(); // (string) HTML  $elementProperty->getValueElement(); // IblockElement -   (E) $elementProperty->getValueSection(); // IblockSection -   (G) $elementProperty->getValueNumber(); // (float)  (int)     /* *    */ $property = $elementProperty->getMeta(); $property->NAME; $property->HINT; } 



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; /* @var $this CBitrixComponentTemplate */ /* @var $component CBitrixComponent */ /* *   ""  ""     */ $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()) { // ok } else { // error } echo Csrf::printInput(); /* * ""  */ $nameField = "csrf"; $token = Bitrix\Main\Context::getCurrent()->getRequest()->getPost($nameField); if (Csrf::validateToken($token)) { // ok } else { // error } $token = Csrf::createToken(); echo "<input type='hidden' name='{$nameField}' value='{$token}'>"; 


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; /* *   URL   */ UrlManager::setBaseUrl("/catalog"); UrlManager::addAlias("sectionList", "index.php"); UrlManager::addAlias("elementList", "#SECTION_CODE#/"); UrlManager::addAlias("elementView", "#SECTION_CODE#/#ELEMENT_CODE#/"); /* *   'alias'    * ,   '/catalog/section1/element1/'    'elementView' */ $alias = UrlManager::parseRequest(); /* *     (   UrlManager   ,     ) */ $folder404 = "/catalog"; $arUrlTemplates = [ "sectionList" => "index.php", "elementList" => "#SECTION_CODE#/", "elementView" => "#SECTION_CODE#/#ELEMENT_CODE#/", ]; $arVariables = []; // UrlManager::$params CComponentEngine::parseComponentPath($folder404, $arUrlTemplates, $arVariables); /* *      */ UrlManager::addParams([ "param1" => "value1", "param2" => "value2", ]); /* *   URL. *        . *     "ELEMENT_CODE" -    */ UrlManager::addParam("SECTION_CODE", "section1"); $url = UrlManager::build("elementView", [ "ELEMENT_CODE" => "element1", ]); // $url: /catalog/section1/element1/ 



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(){ //  }); Event::on(" ", "\ClassName::MethodName", "moduleName"); /* *   */ Event::off(" "); Event::off(" ", 3); //  4-   ( )  /* *   */ Event::trigger(" "); /* *      () */ Event::trigger(" ", $this); 


What's next?


Plans for the near future:


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:

Any interested philanthropist can help (otherwise there is no way), for this you need:

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_release
Marketplace: coming soon

Source: https://habr.com/ru/post/303774/


All Articles