Confession of Bitrix Hater

Something has recently been divorced in a lot of articles about the minuses of Bitrix, and their rebuttals . Since such a booze has gone, then I will add my 5 cents.
In the comments to the articles, they wrote that there was a lack of specificity, examples, and a deeper review.

This article is an attempt to write this review. Although not, it is rather a post of hatred and pain (maybe even a little whining). This is such an extended version of the post about the disadvantages of pistol . I will try to describe most of the things that annoy me and my colleagues in Bitrix. I will try to collect in one post all those disadvantages that cause a lot of pain every day. In the end, I will try to draw conclusions.

Who am i? Yes, in general, an ordinary developer. I have been working with Bitrix since November 2010 (5.5 years). I work only with Bitrix, I have not done a single commercial project on other CMS, I did not use frameworks in creating sites. By occupation, I deal mainly with online stores, their creation, support and development.

TL; DR


Bitrix - UG, do not go into this pool without any special need.

Instead of joining


To begin with, I suggest you conduct a thought experiment. Let's try to take two backend developers of about the same age and about the same work experience (say, 1 - 1.5 years), only so that one of them works all this time with 1C-Bitrix, and the other with Symfony (for example). It is easy to compare with which set of technologies one worked all this time, and with which - another, and what kind of knowledge they eventually gained during this time.

In the case of a Symfony developer, this will be: php5 / 7 + a deep understanding of OOP, generally accepted design patterns (MVC, DI, Factory, Repository at least), the ability to develop Unit tests, use template engines (minimum twig), ORM (with Doctrine), composer , git, PSR standards, console experience and writing console applications, basic web server configuration skills.

In the case of 1C-Bitrix, the developer will be php5, html / css + javascript / jquery (there are no template engines out of the box, and bitrix pops the logic into templates, anyway, you have to tinker with this), maybe git (and it depends a lot on the company , some dinosaurs are still sawing on production via FTP), if you're lucky - a little sql and ... everything?

I understand that this is all very individual and a person’s environment can play a very large role. But here I’m talking about what the system developer is moving out of the box to. In most cases, the Bitrix developer is very much inferior in skills compared to developers for other frameworks / CMS - and this is an indisputable fact. And all because Bitrix initially gives too much freedom in the absence of a clear architecture, documentation, and the right solutions, while Symfony offers everything you need.

Only once did an experienced person from outside the 1C-Bitrix world (in the region) come to our company and he was head and shoulders stronger than his colleagues with the same experience simply due to the fact that earlier they put his brains on the right track.
I myself am like that. Unfortunately, from the very beginning, I was dusted with the same marketing nonsense, and I got into a not very good environment. I myself understand and feel that my colleagues with the same work experience, but in Symfony, have a greater outlook, and this is a very strong side effect of bitrix.
This all suggests that if you want to develop in the world of web development, then definitely not Bitrix should be chosen as the basis.

Comparing the two developers, I want to draw attention to the framework into which the system drives, and the freedom that it provides. That Bitrix, that Symfony - they both provide almost unlimited flexibility, and in principle, on each of them you can create a product of absolutely any complexity. However, the system should help the developer in solving problems, instead of inserting sticks into the wheels. And here Bitrix loses a lot.

Marketing


Just want to say a few words about this, because This is the main component of the success of Bitrix.
We can say that the whole Bitrix, even the documentation for developers, is saturated with the marketing spirit. Even there they write that their product is “so cool that all our partners appreciate and respect it” ( proof , block “Structure”). Bitrix employs good marketers who competently know how to present their product. Once every six months they organize conferences for partners, where they talk about what has been done and what their plans are. As practice shows, these plans never come true on time and very often releases are either incomplete or with a bunch of errors.
As an example, the sensational refactoring of the sale module, the release of which has been postponed for more than a year, and even the latest release date (December 23, 2015 ) failed for 3 months, and released a new store and BUS (Bitrix ed. "Site Management") version 16 only at the end of March 2016. But as a result, after the update, users not only did not receive new features. In most cases, users received an inoperative store, and a pile of new undocumented code in addition.
New instruments are given such high-profile names that everyone is hearing: Composite site - acceleration x100; Highload blocks Bitrix BigData In fact, these words are hiding quite ordinary things that do not match their name.
And this approach can be traced everywhere, unfortunately. Outside, the product looks like a candy, which I bought, put and use. But if you take a step away from the standard delivery with Bitrix - that's it, maintaining functionality during updates turns into hell.
However, first things first, the topic of marketing will still pop up in this post, most likely, more than once.

Architecture


For ten years, Bitrix has been desperately driving himself into a dead end. Each new feature in the product came out in accordance with the interests of the business, without due elaboration from a technical point of view. And, of course, all this grew like a snowball.
If you think about it, then in Bitrix there is no architecture as such. There are not even universally accepted formulated rules that would allow this architecture to be followed. In the course of developers, in the Product Architecture section , it is said that Bitrix follows the MVC architecture and gives a diagram:

Bitrix MVC

I want to say right away that this MVC is very different from the classic version. There is a very strong substitution of concepts, there is really no MVC here, there is simply some abstract division into modules, components, and component templates. And already from these bricks the whole site is built. But each of these bricks can take on different tasks, and therefore they are closely interconnected.
I will try to consider each of these aspects of architecture in more detail.

M - Model, or API


It's hard for me to judge the system API as a model. Yes, the API provides a data access interface and allows you to manipulate it. But the Bitrix API allows you to work not only with data, but also with templates, and with user queries too. Oh well ... that's just my opinion.
There are currently 2 API options in Bitrix. It is conditionally possible to divide them into old and new . The new API is called D7 (honestly, I don’t remember why, but Rizhikov talked about this at one of the partner conferences).

The old API is a collection of antipatterns, terrible examples of bad code. In Bitrix, it was always considered normal to call non-static methods statically, and vice versa, to require a state when this is inappropriate. For example, the well-known CIBlockElement :: GetList is perhaps one of the most frequently used methods in development. Its implementation contains more than 500 lines of code, uses globals, builds terrifying, colossal requests, and contains unrealistic, simply unreadable, undocumented code.
Watching
functionGetList($arOrder=array("SORT"=>"ASC"), $arFilter=array(), $arGroupBy=false, $arNavStartParams=false, $arSelectFields=array()){
        /*
        Filter combinations:
        CHECK_PERMISSIONS="N" - check permissions of the current user to the infoblock
            MIN_PERMISSION="R" - when permissions check, then minimal access level
        SHOW_HISTORY="N" - add history items to list
            SHOW_NEW="N" - if not add history items, then add new, but not published elements
        */global $DB, $USER;
        $MAX_LOCK = intval(COption::GetOptionString("workflow","MAX_LOCK_TIME","60"));
        $uid = is_object($USER)? intval($USER->GetID()): 0;
        $formatActiveDates = CPageOption::GetOptionString("iblock", "FORMAT_ACTIVE_DATES", "-") != "-";
        $shortFormatActiveDates = CPageOption::GetOptionString("iblock", "FORMAT_ACTIVE_DATES", "SHORT");
        $arIblockElementFields = array(
                "ID"=>"BE.ID",
                "TIMESTAMP_X"=>$DB->DateToCharFunction("BE.TIMESTAMP_X"),
                "TIMESTAMP_X_UNIX"=>'UNIX_TIMESTAMP(BE.TIMESTAMP_X)',
                "MODIFIED_BY"=>"BE.MODIFIED_BY",
                "DATE_CREATE"=>$DB->DateToCharFunction("BE.DATE_CREATE"),
                "DATE_CREATE_UNIX"=>'UNIX_TIMESTAMP(BE.DATE_CREATE)',
                "CREATED_BY"=>"BE.CREATED_BY",
                "IBLOCK_ID"=>"BE.IBLOCK_ID",
                "IBLOCK_SECTION_ID"=>"BE.IBLOCK_SECTION_ID",
                "ACTIVE"=>"BE.ACTIVE",
                "ACTIVE_FROM"=>(
                        $formatActiveDates
                        ?
                            $DB->DateToCharFunction("BE.ACTIVE_FROM", $shortFormatActiveDates)
                        :
                            "IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_FROM)>0, ".$DB->DateToCharFunction("BE.ACTIVE_FROM", "FULL").", ".$DB->DateToCharFunction("BE.ACTIVE_FROM", "SHORT").")"
                        ),
                "ACTIVE_TO"=>(
                        $formatActiveDates
                        ?
                            $DB->DateToCharFunction("BE.ACTIVE_TO", $shortFormatActiveDates)
                        :
                            "IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_TO)>0, ".$DB->DateToCharFunction("BE.ACTIVE_TO", "FULL").", ".$DB->DateToCharFunction("BE.ACTIVE_TO", "SHORT").")"
                        ),
                "DATE_ACTIVE_FROM"=>(
                        $formatActiveDates
                        ?
                            $DB->DateToCharFunction("BE.ACTIVE_FROM", $shortFormatActiveDates)
                        :
                            "IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_FROM)>0, ".$DB->DateToCharFunction("BE.ACTIVE_FROM", "FULL").", ".$DB->DateToCharFunction("BE.ACTIVE_FROM", "SHORT").")"
                        ),
                "DATE_ACTIVE_TO"=>(
                        $formatActiveDates
                        ?
                            $DB->DateToCharFunction("BE.ACTIVE_TO", $shortFormatActiveDates)
                        :
                            "IF(EXTRACT(HOUR_SECOND FROM BE.ACTIVE_TO)>0, ".$DB->DateToCharFunction("BE.ACTIVE_TO", "FULL").", ".$DB->DateToCharFunction("BE.ACTIVE_TO", "SHORT").")"
                        ),
                "SORT"=>"BE.SORT",
                "NAME"=>"BE.NAME",
                "PREVIEW_PICTURE"=>"BE.PREVIEW_PICTURE",
                "PREVIEW_TEXT"=>"BE.PREVIEW_TEXT",
                "PREVIEW_TEXT_TYPE"=>"BE.PREVIEW_TEXT_TYPE",
                "DETAIL_PICTURE"=>"BE.DETAIL_PICTURE",
                "DETAIL_TEXT"=>"BE.DETAIL_TEXT",
                "DETAIL_TEXT_TYPE"=>"BE.DETAIL_TEXT_TYPE",
                "SEARCHABLE_CONTENT"=>"BE.SEARCHABLE_CONTENT",
                "WF_STATUS_ID"=>"BE.WF_STATUS_ID",
                "WF_PARENT_ELEMENT_ID"=>"BE.WF_PARENT_ELEMENT_ID",
                "WF_LAST_HISTORY_ID"=>"BE.WF_LAST_HISTORY_ID",
                "WF_NEW"=>"BE.WF_NEW",
                "LOCK_STATUS"=>"if (BE.WF_DATE_LOCK is null, 'green', if(DATE_ADD(BE.WF_DATE_LOCK, interval ".$MAX_LOCK." MINUTE)<now(), 'green', if(BE.WF_LOCKED_BY=".$uid.", 'yellow', 'red')))",
                "WF_LOCKED_BY"=>"BE.WF_LOCKED_BY",
                "WF_DATE_LOCK"=>$DB->DateToCharFunction("BE.WF_DATE_LOCK"),
                "WF_COMMENTS"=>"BE.WF_COMMENTS",
                "IN_SECTIONS"=>"BE.IN_SECTIONS",
                "SHOW_COUNTER"=>"BE.SHOW_COUNTER",
                "SHOW_COUNTER_START"=>$DB->DateToCharFunction("BE.SHOW_COUNTER_START"),
                "CODE"=>"BE.CODE",
                "TAGS"=>"BE.TAGS",
                "XML_ID"=>"BE.XML_ID",
                "EXTERNAL_ID"=>"BE.XML_ID",
                "TMP_ID"=>"BE.TMP_ID",
                "USER_NAME"=>"concat('(',U.LOGIN,') ',ifnull(U.NAME,''),' ',ifnull(U.LAST_NAME,''))",
                "LOCKED_USER_NAME"=>"concat('(',UL.LOGIN,') ',ifnull(UL.NAME,''),' ',ifnull(UL.LAST_NAME,''))",
                "CREATED_USER_NAME"=>"concat('(',UC.LOGIN,') ',ifnull(UC.NAME,''),' ',ifnull(UC.LAST_NAME,''))",
                "LANG_DIR"=>"L.DIR",
                "LID"=>"B.LID",
                "IBLOCK_TYPE_ID"=>"B.IBLOCK_TYPE_ID",
                "IBLOCK_CODE"=>"B.CODE",
                "IBLOCK_NAME"=>"B.NAME",
                "IBLOCK_EXTERNAL_ID"=>"B.XML_ID",
                "DETAIL_PAGE_URL"=>"B.DETAIL_PAGE_URL",
                "LIST_PAGE_URL"=>"B.LIST_PAGE_URL",
                "CANONICAL_PAGE_URL"=>"B.CANONICAL_PAGE_URL",
                "CREATED_DATE"=>$DB->DateFormatToDB("YYYY.MM.DD", "BE.DATE_CREATE"),
                "BP_PUBLISHED"=>"if(BE.WF_STATUS_ID = 1, 'Y', 'N')",
            );
        unset($shortFormatActiveDates);
        unset($formatActiveDates);
        $bDistinct = false;
        CIBlockElement::PrepareGetList(
                $arIblockElementFields,
                $arJoinProps,
                $bOnlyCount,
                $bDistinct,
                $arSelectFields,
                $sSelect,
                $arAddSelectFields,
                $arFilter,
                $sWhere,
                $sSectionWhere,
                $arAddWhereFields,
                $arGroupBy,
                $sGroupBy,
                $arOrder,
                $arSqlOrder,
                $arAddOrderByFields,
                $arIBlockFilter,
                $arIBlockMultProps,
                $arIBlockConvProps,
                $arIBlockAllProps,
                $arIBlockNumProps,
                $arIBlockLongProps
            );
        $arFilterIBlocks = isset($arFilter["IBLOCK_ID"])? array($arFilter["IBLOCK_ID"]): array();
        //******************FROM PART********************************************
        $sFrom = "";
        foreach($arJoinProps["FPS"] as $iblock_id => $iPropCnt)
        {
            $sFrom .= "\t\t\tINNER JOIN b_iblock_element_prop_s".$iblock_id." FPS".$iPropCnt." ON FPS".$iPropCnt.".IBLOCK_ELEMENT_ID = BE.ID\n";
            $arFilterIBlocks[$iblock_id] = $iblock_id;
        }
        foreach($arJoinProps["FP"] as $propID => $db_prop)
        {
            $i = $db_prop["CNT"];
            if($db_prop["bFullJoin"])
                $sFrom .= "\t\t\tINNER JOIN b_iblock_property FP".$i." ON FP".$i.".IBLOCK_ID = B.ID AND ".
                    (
                        IntVal($propID)>0?
                        " FP".$i.".ID=".IntVal($propID)."\n":
                        " FP".$i.".CODE='".$DB->ForSQL($propID, 200)."'\n"
                    );
            else
                $sFrom .= "\t\t\tLEFT JOIN b_iblock_property FP".$i." ON FP".$i.".IBLOCK_ID = B.ID AND ".
                    (
                        IntVal($propID)>0?
                        " FP".$i.".ID=".IntVal($propID)."\n":
                        " FP".$i.".CODE='".$DB->ForSQL($propID, 200)."'\n"
                    );
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        foreach($arJoinProps["FPV"] as $propID => $db_prop)
        {
            $i = $db_prop["CNT"];
            if($db_prop["MULTIPLE"]=="Y")
                $bDistinct = true;
            if($db_prop["VERSION"]==2)
                $strTable = "b_iblock_element_prop_m".$db_prop["IBLOCK_ID"];
            else
                $strTable = "b_iblock_element_property";
            if($db_prop["bFullJoin"])
                $sFrom .= "\t\t\tINNER JOIN ".$strTable." FPV".$i." ON FPV".$i.".IBLOCK_PROPERTY_ID = FP".$db_prop["JOIN"].".ID AND FPV".$i.".IBLOCK_ELEMENT_ID = BE.ID\n";
            else
                $sFrom .= "\t\t\tLEFT JOIN ".$strTable." FPV".$i." ON FPV".$i.".IBLOCK_PROPERTY_ID = FP".$db_prop["JOIN"].".ID AND FPV".$i.".IBLOCK_ELEMENT_ID = BE.ID\n";
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        foreach($arJoinProps["FPEN"] as $propID => $db_prop)
        {
            $i = $db_prop["CNT"];
            if($db_prop["VERSION"] == 2 && $db_prop["MULTIPLE"] == "N")
            {
                if($db_prop["bFullJoin"])
                    $sFrom .= "\t\t\tINNER JOIN b_iblock_property_enum FPEN".$i." ON FPEN".$i.".PROPERTY_ID = ".$db_prop["ORIG_ID"]." AND FPS".$db_prop["JOIN"].".PROPERTY_".$db_prop["ORIG_ID"]." = FPEN".$i.".ID\n";
                else
                    $sFrom .= "\t\t\tLEFT JOIN b_iblock_property_enum FPEN".$i." ON FPEN".$i.".PROPERTY_ID = ".$db_prop["ORIG_ID"]." AND FPS".$db_prop["JOIN"].".PROPERTY_".$db_prop["ORIG_ID"]." = FPEN".$i.".ID\n";
            }
            else
            {
                if($db_prop["bFullJoin"])
                    $sFrom .= "\t\t\tINNER JOIN b_iblock_property_enum FPEN".$i." ON FPEN".$i.".PROPERTY_ID = FPV".$db_prop["JOIN"].".IBLOCK_PROPERTY_ID AND FPV".$db_prop["JOIN"].".VALUE_ENUM = FPEN".$i.".ID\n";
                else
                    $sFrom .= "\t\t\tLEFT JOIN b_iblock_property_enum FPEN".$i." ON FPEN".$i.".PROPERTY_ID = FPV".$db_prop["JOIN"].".IBLOCK_PROPERTY_ID AND FPV".$db_prop["JOIN"].".VALUE_ENUM = FPEN".$i.".ID\n";
            }
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        foreach($arJoinProps["BE"] as $propID => $db_prop)
        {
            $i = $db_prop["CNT"];
            $sFrom .= "\t\t\tLEFT JOIN b_iblock_element BE".$i." ON BE".$i.".ID = ".
                (
                    $db_prop["VERSION"]==2 && $db_prop["MULTIPLE"]=="N"?
                    "FPS".$db_prop["JOIN"].".PROPERTY_".$db_prop["ORIG_ID"]
                    :"FPV".$db_prop["JOIN"].".VALUE_NUM"
                ).
                (
                    $arFilter["SHOW_HISTORY"] != "Y"?
                    " AND ((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)".($arFilter["SHOW_NEW"]=="Y"? " OR BE.WF_NEW='Y'": "").")":
                    ""
                )."\n";
            if($db_prop["bJoinIBlock"])
                $sFrom .= "\t\t\tLEFT JOIN b_iblock B".$i." ON B".$i.".ID = BE".$i.".IBLOCK_ID\n";
            if($db_prop["bJoinSection"])
                $sFrom .= "\t\t\tLEFT JOIN b_iblock_section BS".$i." ON BS".$i.".ID = BE".$i.".IBLOCK_SECTION_ID\n";
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        foreach($arJoinProps["BE_FPS"] as $iblock_id => $db_prop)
        {
            $sFrom .= "\t\t\tLEFT JOIN b_iblock_element_prop_s".$iblock_id." JFPS".$db_prop["CNT"]." ON JFPS".$db_prop["CNT"].".IBLOCK_ELEMENT_ID = BE".$db_prop["JOIN"].".ID\n";
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        foreach($arJoinProps["BE_FP"] as $propID => $db_prop)
        {
            $i = $db_prop["CNT"];
            list($propID, $link) = explode("~", $propID, 2);
            if($db_prop["bFullJoin"])
                $sFrom .= "\t\t\tINNER JOIN b_iblock_property JFP".$i." ON JFP".$i.".IBLOCK_ID = BE".$db_prop["JOIN"].".IBLOCK_ID AND ".
                    (
                        IntVal($propID)>0?
                        " JFP".$i.".ID=".IntVal($propID)."\n":
                        " JFP".$i.".CODE='".$DB->ForSQL($propID, 200)."'\n"
                    );
            else
                $sFrom .= "\t\t\tLEFT JOIN b_iblock_property JFP".$i." ON JFP".$i.".IBLOCK_ID = BE".$db_prop["JOIN"].".IBLOCK_ID AND ".
                    (
                        IntVal($propID)>0?
                        " JFP".$i.".ID=".IntVal($propID)."\n":
                        " JFP".$i.".CODE='".$DB->ForSQL($propID, 200)."'\n"
                    );
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        foreach($arJoinProps["BE_FPV"] as $propID => $db_prop)
        {
            $i = $db_prop["CNT"];
            list($propID, $link) = explode("~", $propID, 2);
            if($db_prop["MULTIPLE"]=="Y")
                $bDistinct = true;
            if($db_prop["VERSION"]==2)
                $strTable = "b_iblock_element_prop_m".$db_prop["IBLOCK_ID"];
            else
                $strTable = "b_iblock_element_property";
            if($db_prop["bFullJoin"])
                $sFrom .= "\t\t\tINNER JOIN ".$strTable." JFPV".$i." ON JFPV".$i.".IBLOCK_PROPERTY_ID = JFP".$db_prop["JOIN"].".ID AND JFPV".$i.".IBLOCK_ELEMENT_ID = BE".$db_prop["BE_JOIN"].".ID\n";
            else
                $sFrom .= "\t\t\tLEFT JOIN ".$strTable." JFPV".$i." ON JFPV".$i.".IBLOCK_PROPERTY_ID = JFP".$db_prop["JOIN"].".ID AND JFPV".$i.".IBLOCK_ELEMENT_ID = BE".$db_prop["BE_JOIN"].".ID\n";
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        foreach($arJoinProps["BE_FPEN"] as $propID => $db_prop)
        {
            $i = $db_prop["CNT"];
            list($propID, $link) = explode("~", $propID, 2);
            if($db_prop["VERSION"] == 2 && $db_prop["MULTIPLE"] == "N")
            {
                if($db_prop["bFullJoin"])
                    $sFrom .= "\t\t\tINNER JOIN b_iblock_property_enum JFPEN".$i." ON JFPEN".$i.".PROPERTY_ID = ".$db_prop["ORIG_ID"]." AND JFPS".$db_prop["JOIN"].".PROPERTY_".$db_prop["ORIG_ID"]." = JFPEN".$i.".ID\n";
                else
                    $sFrom .= "\t\t\tLEFT JOIN b_iblock_property_enum JFPEN".$i." ON JFPEN".$i.".PROPERTY_ID = ".$db_prop["ORIG_ID"]." AND JFPS".$db_prop["JOIN"].".PROPERTY_".$db_prop["ORIG_ID"]." = JFPEN".$i.".ID\n";
            }
            else
            {
                if($db_prop["bFullJoin"])
                    $sFrom .= "\t\t\tINNER JOIN b_iblock_property_enum JFPEN".$i." ON JFPEN".$i.".PROPERTY_ID = JFPV".$db_prop["JOIN"].".IBLOCK_PROPERTY_ID AND JFPV".$db_prop["JOIN"].".VALUE_ENUM = JFPEN".$i.".ID\n";
                else
                    $sFrom .= "\t\t\tLEFT JOIN b_iblock_property_enum JFPEN".$i." ON JFPEN".$i.".PROPERTY_ID = JFPV".$db_prop["JOIN"].".IBLOCK_PROPERTY_ID AND JFPV".$db_prop["JOIN"].".VALUE_ENUM = JFPEN".$i.".ID\n";
            }
            if($db_prop["IBLOCK_ID"])
                $arFilterIBlocks[$db_prop["IBLOCK_ID"]] = $db_prop["IBLOCK_ID"];
        }
        if(strlen($arJoinProps["BES"]))
        {
            $sFrom .= "\t\t\t".$arJoinProps["BES"]."\n";
        }
        if(strlen($arJoinProps["FC"]))
        {
            $sFrom .= "\t\t\t".$arJoinProps["FC"]."\n";
            $bDistinct = $bDistinct || (isset($arJoinProps["FC_DISTINCT"]) && $arJoinProps["FC_DISTINCT"] == "Y");
        }
        if($arJoinProps["RV"])
            $sFrom .= "\t\t\tLEFT JOIN b_rating_voting RV ON RV.ENTITY_TYPE_ID = 'IBLOCK_ELEMENT' AND RV.ENTITY_ID = BE.ID\n";
        if($arJoinProps["RVU"])
            $sFrom .= "\t\t\tLEFT JOIN b_rating_vote RVU ON RVU.ENTITY_TYPE_ID = 'IBLOCK_ELEMENT' AND RVU.ENTITY_ID = BE.ID AND RVU.USER_ID = ".$uid."\n";
        if($arJoinProps["RVV"])
            $sFrom .= "\t\t\t".($arJoinProps["RVV"]["bFullJoin"]? "INNER": "LEFT")." JOIN b_rating_vote RVV ON RVV.ENTITY_TYPE_ID = 'IBLOCK_ELEMENT' AND RVV.ENTITY_ID = BE.ID\n";
        //******************END OF FROM PART********************************************
        $bCatalogSort = false;
        if(count($arAddSelectFields)>0 || count($arAddWhereFields)>0 || count($arAddOrderByFields)>0)
        {
            if(CModule::IncludeModule("catalog"))
            {
                $res_catalog = CCatalogProduct::GetQueryBuildArrays($arAddOrderByFields, $arAddWhereFields, $arAddSelectFields);
                if(
                    $sGroupBy==""
                    && !$bOnlyCount
                    && !(is_object($this) && isset($this->strField))
                )
                    $sSelect .= $res_catalog["SELECT"]." ";
                $sFrom .= str_replace("LEFT JOIN", "\n\t\t\tLEFT JOIN", $res_catalog["FROM"])."\n";
                //$sWhere .= $res_catalog["WHERE"]." "; moved to MkFilterif(is_array($res_catalog["ORDER"]) && count($res_catalog["ORDER"]))
                {
                    $bCatalogSort = true;
                    foreach($res_catalog["ORDER"] as $i=>$val)
                        $arSqlOrder[$i] = $val;
                }
            }
        }
        $i = array_search("CREATED_BY_FORMATTED", $arSelectFields);
        if ($i !== false)
        {
            if (
                $sSelect
                && $sGroupBy==""
                && !$bOnlyCount
                && !(is_object($this) && isset($this->strField))
            )
            {
                $sSelect .= ",UC.NAME UC_NAME, UC.LAST_NAME UC_LAST_NAME, UC.SECOND_NAME UC_SECOND_NAME, UC.EMAIL UC_EMAIL, UC.ID UC_ID, UC.LOGIN UC_LOGIN";
            }
            else
            {
                unset($arSelectFields[$i]);
            }
        }
        $sOrderBy = "";
        foreach($arSqlOrder as $i=>$val)
        {
            if(strlen($val))
            {
                if($sOrderBy=="")
                    $sOrderBy = " ORDER BY ";
                else
                    $sOrderBy .= ",";
                $sOrderBy .= $val." ";
            }
        }
        $sSelect = trim($sSelect, ", \t\n\r");
        if(strlen($sSelect) <= 0)
            $sSelect = "0 as NOP ";
        $bDistinct = $bDistinct || (isset($arFilter["INCLUDE_SUBSECTIONS"]) && $arFilter["INCLUDE_SUBSECTIONS"] == "Y");
        if($bDistinct)
            $sSelect = str_replace("%%_DISTINCT_%%", "DISTINCT", $sSelect);
        else
            $sSelect = str_replace("%%_DISTINCT_%%", "", $sSelect);
        $sFrom = "
            b_iblock B
            INNER JOIN b_lang L ON B.LID=L.LID
            INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
            ".ltrim($sFrom, "\t\n")
            .(in_array("USER_NAME", $arSelectFields)? "\t\t\tLEFT JOIN b_user U ON U.ID=BE.MODIFIED_BY\n": "")
            .(in_array("LOCKED_USER_NAME", $arSelectFields)? "\t\t\tLEFT JOIN b_user UL ON UL.ID=BE.WF_LOCKED_BY\n": "")
            .(in_array("CREATED_USER_NAME", $arSelectFields) || in_array("CREATED_BY_FORMATTED", $arSelectFields)? "\t\t\tLEFT JOIN b_user UC ON UC.ID=BE.CREATED_BY\n": "")."
        ";
        $strSql = "
            FROM ".$sFrom."
            WHERE 1=1 "
            .$sWhere."
            ".$sGroupBy."
        ";
        if(isset($this) && is_object($this) && isset($this->strField))
        {
            $this->sFrom = $sFrom;
            $this->sWhere = $sWhere;
            return"SELECT ".$sSelect.$strSql;
        }
        if($bOnlyCount)
        {
            $res = $DB->Query("SELECT ".$sSelect.$strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
            $res = $res->Fetch();
            return $res["CNT"];
        }
        if(is_array($arNavStartParams))
        {
            $nTopCount = intval($arNavStartParams["nTopCount"]);
            $nElementID = intval($arNavStartParams["nElementID"]);
            if($nTopCount > 0)
            {
                $strSql = "SELECT ".$sSelect.$strSql.$sOrderBy." LIMIT ".$nTopCount;
                $res = $DB->Query($strSql);
            }
            elseif(
                $nElementID > 0
                && $sGroupBy == ""
                && $sOrderBy != ""
                && strpos($sSelect, "BE.ID") !== false
                && !$bCatalogSort
            )
            {
                $nPageSize = intval($arNavStartParams["nPageSize"]);
                if($nPageSize > 0)
                {
                    $DB->Query("SET @rank_e=0");
                    $DB->Query("SET @rank_r=0");
                    $DB->Query("
                        SELECT
                            ".$sSelect."
                            ,@rank_r:=@rank_r+1 AS rank1
                            ,if (BE.ID = ".$nElementID.", @rank_e:=@rank_r, null) rank2
                        ".$strSql.$sOrderBy."
                    ");
                    $DB->Query("SET @rank_r=0");
                    $res = $DB->Query("
                        SELECT *
                        FROM (
                            SELECT
                                ".$sSelect."
                                ,@rank_r:=@rank_r+1 AS RANK
                            ".$strSql.$sOrderBy."
                            LIMIT 18446744073709551615
                        ) el0
                        WHERE el0.RANK between @rank_e-$nPageSize and @rank_e+$nPageSize
                    ");
                }
                else
                {
                    $DB->Query("SET @rank=0");
                    $res = $DB->Query("
                        SELECT *
                        FROM (
                            SELECT
                                ".$sSelect."
                                ,@rank:=@rank+1 AS RANK
                            ".$strSql.$sOrderBy."
                            LIMIT 18446744073709551615
                        ) el0
                        WHERE el0.ID = ".$nElementID."
                    ");
                }
            }
            else
            {
                if($sGroupBy == "")
                {
                    $res_cnt = $DB->Query("SELECT COUNT(".($bDistinct? "DISTINCT BE.ID": "'x'").") as C ".$strSql);
                    $res_cnt = $res_cnt->Fetch();
                    $cnt = $res_cnt["C"];
                }
                else
                {
                    $res_cnt = $DB->Query("SELECT 'x' ".$strSql);
                    $cnt = $res_cnt->SelectedRowsCount();
                }
                $strSql = "SELECT ".$sSelect.$strSql.$sOrderBy;
                $res = new CDBResult();
                $res->NavQuery($strSql, $cnt, $arNavStartParams);
            }
        }
        else//if(is_array($arNavStartParams))
        {
            $strSql = "SELECT ".$sSelect.$strSql.$sOrderBy;
            $res = $DB->Query($strSql, false, "FILE: ".__FILE__."<br> LINE: ".__LINE__);
        }
        $res = new CIBlockResult($res);
        $res->SetIBlockTag($arFilterIBlocks);
        $res->arIBlockMultProps = $arIBlockMultProps;
        $res->arIBlockConvProps = $arIBlockConvProps;
        $res->arIBlockAllProps  = $arIBlockAllProps;
        $res->arIBlockNumProps = $arIBlockNumProps;
        $res->arIBlockLongProps = $arIBlockLongProps;
        return $res;
    }



As you might guess, this method gets a list of infoblock elements from the database, and to get the list you do not need to create an instance of the CIBlockElement class. However, to add an infoblock element, a state is necessary, and only in order to write information about the last error that occurred in the public property of the class.

In the old API, global variables such as $ APPLICATION, $ USER, $ DB are very actively used. They are instances of certain classes, and in the documentation they used to be proudly called singletons, though now I have not found these words.
In order to generate an error, for example, in event handlers, you need to use the $ APPLICATION-> ThrowException () method, which actually does not throw an exception.
publicfunctionThrowException($msg, $id = false){
        $this->ResetException();
        if(is_object($msg) && (is_subclass_of($msg, 'CApplicationException') || (strtolower(get_class($msg))=='capplicationexception')))
            $this->LAST_ERROR = $msg;
        else$this->LAST_ERROR = new CApplicationException($msg, $id);
    }


And yes - all this beauty is still used in the development of new projects, because D7 does not yet support all the features of the old API. The same infoblock module still allows you to perform only a selection of entities, and not the whole. It is not yet possible to create a new element, or update an existing one using the new API.

The new API is already slightly different from the old. Firstly, all the code from the new kernel is laid out by namespace, where a clear dependence on the module can be traced. For example, the analogue of CIBlockElement :: GetList from the new kernel is Bitrix \ Iblock \ ElementTable :: getList, where the root namespace is the name of the vendor, and the next is the name of the module. In order for this to work, Bitrix wrote their autoloader \ Bitrix \ Main \ Loader :: autoLoad, which is completely incompatible with PSR-0/4.

Actually, autoloader code as a single function
publicstaticfunctionautoLoad($className){
		$file = ltrim($className, "\\");    // fix web env
		$file = strtr($file, static::ALPHA_UPPER, static::ALPHA_LOWER);
		static $documentRoot = null;
		if ($documentRoot === null)
			$documentRoot = static::getDocumentRoot();
		if (isset(self::$arAutoLoadClasses[$file]))
		{
			$pathInfo = self::$arAutoLoadClasses[$file];
			if ($pathInfo["module"] != "")
			{
				$m = $pathInfo["module"];
				$h = isset(self::$arLoadedModulesHolders[$m]) ? self::$arLoadedModulesHolders[$m] : 'bitrix';
				include_once($documentRoot."/".$h."/modules/".$m."/" .$pathInfo["file"]);
			}
			else
			{
				require_once($documentRoot.$pathInfo["file"]);
			}
			return;
		}
		if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $file))
			return;
		if (substr($file, -5) == "table")
			$file = substr($file, 0, -5);
		$file = str_replace('\\', '/', $file);
		$arFile = explode("/", $file);
		if ($arFile[0] === "bitrix")
		{
			array_shift($arFile);
			if (empty($arFile))
				return;
			$module = array_shift($arFile);
			if ($module == null || empty($arFile))
				return;
		}
		else
		{
			$module1 = array_shift($arFile);
			$module2 = array_shift($arFile);
			if ($module1 == null || $module2 == null || empty($arFile))
				return;
			$module = $module1.".".$module2;
		}
		if (!isset(self::$arLoadedModulesHolders[$module]))
			return;
		$filePath = $documentRoot."/".self::$arLoadedModulesHolders[$module]."/modules/".$module."/lib/".implode("/", $arFile).".php";
		if (file_exists($filePath))
			require_once($filePath);
	}



The new API traces a lot of love for Singleton:
  • \ Bitrix \ Main \ Application :: getInstance - application instance
  • \ Bitrix \ Main \ Config \ Configuration :: getInstance - class instance for managing configs
  • \ Bitrix \ Main \ Page \ Asset :: getInstance - instance of the Asset manager
  • \ Bitrix \ Main \ EventManager :: getInstance - event manager


Perhaps all this in the future will grow into its ServiceLayer (there is a certain \ Bitrix \ Main \ ServiceManager in the new kernel, which is not yet used and is not documented). But so far there is little hope.

ORM is another of the D7 innovations, and this is something that can claim to be the real model! You can distinguish an ORM entity class from any other class by its name. An entity class should always end with a Table (ElementTable, SectionTable, OrderTable, etc.). Moreover, paradox, the name of the file with the ORM class of the entity should not end with Table. For example, for ElementTable we must create an element.php file. The screenshot below shows the contents of the lib directory (only in this directory does D7 autoload work) of the iblock module. Try by eye to determine what are ORM entities and what are ordinary classes with business logic.

Iblock module structure

ORM, by and large, so far is nothing special. It allows you to describe database tables in the form of classes and allows you to perform queries on these tables, link them together. No ActiveRecord and Repository is and is not expected.

An example of a typical ORM entity class for an infoblock element
<?namespaceBitrix\Iblock;
useBitrix\Main;
useBitrix\Main\Localization\Loc;
Loc::loadMessages(__FILE__);
/**
 * Class ElementTable
 *
 * Fields:
 * <ul>
 * <li> ID int mandatory
 * <li> TIMESTAMP_X datetime optional
 * <li> MODIFIED_BY int optional
 * <li> DATE_CREATE datetime optional
 * <li> CREATED_BY int optional
 * <li> IBLOCK_ID int mandatory
 * <li> IBLOCK_SECTION_ID int optional
 * <li> ACTIVE bool optional default 'Y'
 * <li> ACTIVE_FROM datetime optional
 * <li> ACTIVE_TO datetime optional
 * <li> SORT int optional default 500
 * <li> NAME string(255) mandatory
 * <li> PREVIEW_PICTURE int optional
 * <li> PREVIEW_TEXT string optional
 * <li> PREVIEW_TEXT_TYPE enum ('text', 'html') optional default 'text'
 * <li> DETAIL_PICTURE int optional
 * <li> DETAIL_TEXT string optional
 * <li> DETAIL_TEXT_TYPE enum ('text', 'html') optional default 'text'
 * <li> SEARCHABLE_CONTENT string optional
 * <li> WF_STATUS_ID int optional default 1
 * <li> WF_PARENT_ELEMENT_ID int optional
 * <li> WF_NEW enum ('N', 'Y') optional
 * <li> WF_LOCKED_BY int optional
 * <li> WF_DATE_LOCK datetime optional
 * <li> WF_COMMENTS string optional
 * <li> IN_SECTIONS bool optional default 'N'
 * <li> XML_ID string(255) optional
 * <li> CODE string(255) optional
 * <li> TAGS string(255) optional
 * <li> TMP_ID string(40) optional
 * <li> WF_LAST_HISTORY_ID int optional
 * <li> SHOW_COUNTER int optional
 * <li> SHOW_COUNTER_START datetime optional
 * <li> PREVIEW_PICTURE_FILE reference to {@link \Bitrix\File\FileTable}
 * <li> DETAIL_PICTURE_FILE reference to {@link \Bitrix\File\FileTable}
 * <li> IBLOCK reference to {@link \Bitrix\Iblock\IblockTable}
 * <li> WF_PARENT_ELEMENT reference to {@link \Bitrix\Iblock\IblockElementTable}
 * <li> IBLOCK_SECTION reference to {@link \Bitrix\Iblock\IblockSectionTable}
 * <li> MODIFIED_BY_USER reference to {@link \Bitrix\User\UserTable}
 * <li> CREATED_BY_USER reference to {@link \Bitrix\User\UserTable}
 * <li> WF_LOCKED_BY_USER reference to {@link \Bitrix\User\UserTable}
 * </ul>
 *
 * @package Bitrix\Iblock
 **/classElementTableextendsMain\Entity\DataManager{
	const TYPE_TEXT = 'text';
	const TYPE_HTML = 'html';
	/**
	 * Returns DB table name for entity.
	 *
	 * @return string
	 */publicstaticfunctiongetTableName(){
		return'b_iblock_element';
	}
	/**
	 * Returns entity map definition.
	 *
	 * @return array
	 */publicstaticfunctiongetMap(){
		returnarray(
			'ID' => new Main\Entity\IntegerField('ID', array(
				'primary' => true,
				'autocomplete' => true,
				'title' => Loc::getMessage('ELEMENT_ENTITY_ID_FIELD'),
			)),
			'TIMESTAMP_X' => new Main\Entity\DatetimeField('TIMESTAMP_X', array(
				'default_value' => new Main\Type\DateTime(),
				'title' => Loc::getMessage('ELEMENT_ENTITY_TIMESTAMP_X_FIELD'),
			)),
			'MODIFIED_BY' => new Main\Entity\IntegerField('MODIFIED_BY', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_MODIFIED_BY_FIELD'),
			)),
			'DATE_CREATE' => new Main\Entity\DatetimeField('DATE_CREATE', array(
				'default_value' => new Main\Type\DateTime(),
				'title' => Loc::getMessage('ELEMENT_ENTITY_DATE_CREATE_FIELD'),
			)),
			'CREATED_BY' => new Main\Entity\IntegerField('CREATED_BY', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_CREATED_BY_FIELD'),
			)),
			'IBLOCK_ID' => new Main\Entity\IntegerField('IBLOCK_ID', array(
				'required' => true,
				'title' => Loc::getMessage('ELEMENT_ENTITY_IBLOCK_ID_FIELD'),
			)),
			'IBLOCK_SECTION_ID' => new Main\Entity\IntegerField('IBLOCK_SECTION_ID', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_IBLOCK_SECTION_ID_FIELD'),
			)),
			'ACTIVE' => new Main\Entity\BooleanField('ACTIVE', array(
				'values' => array('N', 'Y'),
				'default_value' => 'Y',
				'title' => Loc::getMessage('ELEMENT_ENTITY_ACTIVE_FIELD'),
			)),
			'ACTIVE_FROM' => new Main\Entity\DatetimeField('ACTIVE_FROM', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_ACTIVE_FROM_FIELD'),
			)),
			'ACTIVE_TO' => new Main\Entity\DatetimeField('ACTIVE_TO', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_ACTIVE_TO_FIELD'),
			)),
			'SORT' => new Main\Entity\IntegerField('SORT', array(
				'default_value' => 500,
				'title' => Loc::getMessage('ELEMENT_ENTITY_SORT_FIELD'),
			)),
			'NAME' => new Main\Entity\StringField('NAME', array(
				'required' => true,
				'validation' => array(__CLASS__, 'validateName'),
				'title' => Loc::getMessage('ELEMENT_ENTITY_NAME_FIELD'),
			)),
			'PREVIEW_PICTURE' => new Main\Entity\IntegerField('PREVIEW_PICTURE', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_PREVIEW_PICTURE_FIELD'),
			)),
			'PREVIEW_TEXT' => new Main\Entity\TextField('PREVIEW_TEXT', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_PREVIEW_TEXT_FIELD'),
			)),
			'PREVIEW_TEXT_TYPE' => new Main\Entity\EnumField('PREVIEW_TEXT_TYPE', array(
				'values' => array(self::TYPE_TEXT, self::TYPE_HTML),
				'default_value' => self::TYPE_TEXT,
				'title' => Loc::getMessage('ELEMENT_ENTITY_PREVIEW_TEXT_TYPE_FIELD'),
			)),
			'DETAIL_PICTURE' => new Main\Entity\IntegerField('DETAIL_PICTURE', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_DETAIL_PICTURE_FIELD'),
			)),
			'DETAIL_TEXT' => new Main\Entity\TextField('DETAIL_TEXT', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_DETAIL_TEXT_FIELD'),
			)),
			'DETAIL_TEXT_TYPE' => new Main\Entity\EnumField('DETAIL_TEXT_TYPE', array(
				'values' => array(self::TYPE_TEXT, self::TYPE_HTML),
				'default_value' => self::TYPE_TEXT,
				'title' => Loc::getMessage('ELEMENT_ENTITY_DETAIL_TEXT_TYPE_FIELD'),
			)),
			'SEARCHABLE_CONTENT' => new Main\Entity\TextField('SEARCHABLE_CONTENT', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_SEARCHABLE_CONTENT_FIELD'),
			)),
			'WF_STATUS_ID' => new Main\Entity\IntegerField('WF_STATUS_ID', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_WF_STATUS_ID_FIELD'),
			)),
			'WF_PARENT_ELEMENT_ID' => new Main\Entity\IntegerField('WF_PARENT_ELEMENT_ID', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_WF_PARENT_ELEMENT_ID_FIELD'),
			)),
			'WF_NEW' => new Main\Entity\EnumField('WF_NEW', array(
				'values' => array('N', 'Y'),
				'title' => Loc::getMessage('ELEMENT_ENTITY_WF_NEW_FIELD'),
			)),
			'WF_LOCKED_BY' => new Main\Entity\IntegerField('WF_LOCKED_BY', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_WF_LOCKED_BY_FIELD'),
			)),
			'WF_DATE_LOCK' => new Main\Entity\DatetimeField('WF_DATE_LOCK', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_WF_DATE_LOCK_FIELD'),
			)),
			'WF_COMMENTS' => new Main\Entity\TextField('WF_COMMENTS', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_WF_COMMENTS_FIELD'),
			)),
			'IN_SECTIONS' => new Main\Entity\BooleanField('IN_SECTIONS', array(
				'values' => array('N', 'Y'),
				'title' => Loc::getMessage('ELEMENT_ENTITY_IN_SECTIONS_FIELD'),
			)),
			'XML_ID' => new Main\Entity\StringField('XML_ID', array(
				'validation' => array(__CLASS__, 'validateXmlId'),
				'title' => Loc::getMessage('ELEMENT_ENTITY_XML_ID_FIELD'),
			)),
			'CODE' => new Main\Entity\StringField('CODE', array(
				'validation' => array(__CLASS__, 'validateCode'),
				'title' => Loc::getMessage('ELEMENT_ENTITY_CODE_FIELD'),
			)),
			'TAGS' => new Main\Entity\StringField('TAGS', array(
				'validation' => array(__CLASS__, 'validateTags'),
				'title' => Loc::getMessage('ELEMENT_ENTITY_TAGS_FIELD'),
			)),
			'TMP_ID' => new Main\Entity\StringField('TMP_ID', array(
				'validation' => array(__CLASS__, 'validateTmpId'),
				'title' => Loc::getMessage('ELEMENT_ENTITY_TMP_ID_FIELD'),
			)),
			'SHOW_COUNTER' => new Main\Entity\IntegerField('SHOW_COUNTER', array(
				'default_value' => 0,
				'title' => Loc::getMessage('ELEMENT_ENTITY_SHOW_COUNTER_FIELD'),
			)),
			'SHOW_COUNTER_START' => new Main\Entity\DatetimeField('SHOW_COUNTER_START', array(
				'title' => Loc::getMessage('ELEMENT_ENTITY_SHOW_COUNTER_START_FIELD'),
			)),
			'PREVIEW_PICTURE_FILE' => new Main\Entity\ReferenceField(
				'PREVIEW_PICTURE_FILE',
				'Bitrix\File\File',
				array('=this.PREVIEW_PICTURE' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
			'DETAIL_PICTURE_FILE' => new Main\Entity\ReferenceField(
				'DETAIL_PICTURE_FILE',
				'Bitrix\File\File',
				array('=this.DETAIL_PICTURE' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
			'IBLOCK' => new Main\Entity\ReferenceField(
				'IBLOCK',
				'Bitrix\Iblock\Iblock',
				array('=this.IBLOCK_ID' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
			'WF_PARENT_ELEMENT' => new Main\Entity\ReferenceField(
				'WF_PARENT_ELEMENT',
				'Bitrix\Iblock\Element',
				array('=this.WF_PARENT_ELEMENT_ID' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
			'IBLOCK_SECTION' => new Main\Entity\ReferenceField(
				'IBLOCK_SECTION',
				'Bitrix\Iblock\Section',
				array('=this.IBLOCK_SECTION_ID' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
			'MODIFIED_BY_USER' => new Main\Entity\ReferenceField(
				'MODIFIED_BY_USER',
				'Bitrix\User\User',
				array('=this.MODIFIED_BY' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
			'CREATED_BY_USER' => new Main\Entity\ReferenceField(
				'CREATED_BY_USER',
				'Bitrix\User\User',
				array('=this.CREATED_BY' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
			'WF_LOCKED_BY_USER' => new Main\Entity\ReferenceField(
				'WF_LOCKED_BY_USER',
				'Bitrix\User\User',
				array('=this.WF_LOCKED_BY' => 'ref.ID'),
				array('join_type' => 'LEFT')
			),
		);
	}
	/**
	 * Returns validators for NAME field.
	 *
	 * @return array
	 */publicstaticfunctionvalidateName(){
		returnarray(
			new Main\Entity\Validator\Length(null, 255),
		);
	}
	/**
	 * Returns validators for XML_ID field.
	 *
	 * @return array
	 */publicstaticfunctionvalidateXmlId(){
		returnarray(
			new Main\Entity\Validator\Length(null, 255),
		);
	}
	/**
	 * Returns validators for CODE field.
	 *
	 * @return array
	 */publicstaticfunctionvalidateCode(){
		returnarray(
			new Main\Entity\Validator\Length(null, 255),
		);
	}
	/**
	 * Returns validators for TAGS field.
	 *
	 * @return array
	 */publicstaticfunctionvalidateTags(){
		returnarray(
			new Main\Entity\Validator\Length(null, 255),
		);
	}
	/**
	 * Returns validators for TMP_ID field.
	 *
	 * @return array
	 */publicstaticfunctionvalidateTmpId(){
		returnarray(
			new Main\Entity\Validator\Length(null, 40),
		);
	}
	/**
	 * Add iblock element.
	 *
	 * @param array $data			Element data.
	 * @return Main\Entity\AddResult
	 */publicstaticfunctionadd(array $data){
		$result = new Main\Entity\AddResult();
		$result->addError(new Main\Entity\EntityError(
			Loc::getMessage('ELEMENT_ENTITY_MESS_ADD_BLOCKED')
		));
		return $result;
	}
	/**
	 * Updates iblock element by primary key.
	 *
	 * @param mixed $primary		Element primary key.
	 * @param array $data			Element data.
	 * @return Main\Entity\UpdateResult
	 */publicstaticfunctionupdate($primary, array $data){
		$result = new Main\Entity\UpdateResult();
		$result->addError(new Main\Entity\EntityError(
			Loc::getMessage('ELEMENT_ENTITY_MESS_UPDATE_BLOCKED')
		));
		return $result;
	}
	/**
	 * Deletes iblock element by primary key.
	 *
	 * @param mixed $primary		Element primary key.
	 * @return Main\Entity\DeleteResult
	 */publicstaticfunctiondelete($primary){
		$result = new Main\Entity\DeleteResult();
		$result->addError(new Main\Entity\EntityError(
			Loc::getMessage('ELEMENT_ENTITY_MESS_DELETE_BLOCKED')
		));
		return $result;
	}
}


And an example of working with this entity
//Выборка данных
$dbElements = Bitrix\Iblock\ElementTable::query()
	->setFilter(['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'ACTIVE' => 'Y'])
	->setSelect(['NAME', 'ID', 'DETAIL_PAGE_URL', 'DATE_ACTIVE_FROM'])
	->addSelect('IBLOCK_SECTION_ID', 'PARENT_SECTION')
	->setLimit(10)
	->addOrder('id', 'DESC')
	->exec();
while ($arElement = $dbElements->fetch()) {
	echo"{$arElement['NAME']} - " . $arElement['DATE_ACTIVE_FROM']->format('d.m.Y H:i:s');
}
//Добавление записи
$addResult = Bitrix\Iblock\ElementTable::add([
	'NAME' => 'Название нового элемента', 
	'IBLOCK_ID' => CATALOG_IBLOCK_ID
]);
if (!$addResult->isSuccess()) {
	echo implode('<br>' ,$addResult->getErrorMessages());
}



Bitrix is ​​very proud of its module Highload-blocks, which is completely written using D7.
Previously, they had only information blocks as a storage for an arbitrary set of information. The infoblock, for those who are not in the know, is such an entity that is stored in the database as a complex of several tables (1 table on the “base” fields of the infoblock element and up to 2 tables on the properties of the infoblock element). All base fields of all elementsinfoblocks are stored in one table. If you have 15 information blocks, each of which will have 500k elements, all these elements will be in fact in one table. Additional properties of infoblock elements are joined from other tables. If these are infoblocks of the first version, then all the properties of all infoblocks also lie in one table, and in the case of infoblocks 2.0 (hello, marketing), the properties of each infoblock are already divided into different tables.
And all this business naturally very strongly slowed down already on rather small data sets. 400k elements in one infoblock are already pretty much slowing down the admin panel. Marketers in Bitrix thought, and washed down Highload blocks! The difference in implementation between the usual infoblocks is minimal. Now for each highload-block its own table is created + in addition another table is created to store multiple values. They called the usual approach to creating a regular table in the database the proud name highload, simply because it slows down less than regular info blocks!
In addition, inside the module, in order for it to work according to D7, entity classes are generated dynamically and are eval on every hit. Here is such a highload.
Look at it
publicstaticfunctioncompileEntity($hlblock){
        global $USER_FIELD_MANAGER;
        // generate entity & data manager
        $fieldsMap = array();
        // add ID
        $fieldsMap['ID'] = array(
            'data_type' => 'integer',
            'primary' => true,
            'autocomplete' => true
        );
        // build datamanager class
        $entity_name = $hlblock['NAME'];
        $entity_data_class = $hlblock['NAME'];
        if (!preg_match('/^[a-z0-9_]+$/i', $entity_data_class))
        {
            thrownew Main\SystemException(sprintf(
                'Invalid entity name `%s`.', $entity_data_class
            ));
        }
        $entity_data_class .= 'Table';
        if (class_exists($entity_data_class))
        {
            // rebuild if it's already exists
            Entity\Base::destroy($entity_data_class);
        }
        else
        {
            $entity_table_name = $hlblock['TABLE_NAME'];
            // make with an empty map
            $eval = '
                class '.$entity_data_class.' extends '.__NAMESPACE__.'\DataManager
                {
                    public static function getTableName()
                    {
                        return '.var_export($entity_table_name, true).';
                    }
                    public static function getMap()
                    {
                        return '.var_export($fieldsMap, true).';
                    }
                    public static function getHighloadBlock()
                    {
                        return '.var_export($hlblock, true).';
                    }
                }
            ';
            eval($eval);
        }
        // then configure and attach fields/** @var \Bitrix\Main\Entity\DataManager $entity_data_class */
        $entity = $entity_data_class::getEntity();
        $uFields = $USER_FIELD_MANAGER->getUserFields('HLBLOCK_'.$hlblock['ID']);
        foreach ($uFields as $uField)
        {
            if ($uField['MULTIPLE'] == 'N')
            {
                // just add single field
                $field = $USER_FIELD_MANAGER->getEntityField($uField, $uField['FIELD_NAME']);
                $entity->addField($field);
                foreach ($USER_FIELD_MANAGER->getEntityReferences($uField, $field) as $reference)
                {
                    $entity->addField($reference);
                }
            }
            else
            {
                // build utm entitystatic::compileUtmEntity($entity, $uField);
            }
        }
        return Entity\Base::getInstance($entity_name);
    }


Damn it, but these same highload blocks by no means can act as an alternative to ordinary info blocks. It turns out they were invented only in order to store reference non-hierarchical data. In addition, the module still does not support such necessary functions in the admin panel as filtering by the “Date” field; you cannot name the HL block entity with some kind of human-readable name so that the administrator does not get scared every time he goes to the entity editing page, for example, BrandReference. All this suggests that this business was conceived for the sake of an alternative to slow infoblocks, but they didn’t manage to finish it (either they didn’t master it, or went against the interests of the business), and as a result, they semi-finished functionality as a new chip, and the marketers combed it beautifully filed this idea.

C - Controller, or component


The usual component in the bitrix can be compared with widgets from Yii. This is a certain container, isolated from all other containers, which takes some parameters as input, does some work, and connects the view with the result of the work. Bitrix developers are deeply convinced that the components that they provide out of the box solve most of the problems that their colleagues face. But, as usual, developers do not like anything always, and the capabilities of standard components are always "a little" lacking. Therefore, Bitrixoids decided to give developers the opportunity to modify the result of the component ... using the view. In the directory of the component template, you can create a file result_modifier.php, in which you can supplement the result of the component with your data. And if you suddenly want to use this data in another template, You will have to copy-paste this file (well, or to exclude this file from another template). I was always tormented by the question - why this pathos? Why not add a bunch of requests directly in the php template? The difference is small.
What am I talking about templates in the section on controllers ...

There are 2 types of components 2.0 in Bitrix (again, hello marketing) - ordinary and complex. The usual component is a widget. The complex component is a certain controller + router, which, based on the URL, understands which page with a set of widgets to display. The procedure is approximately the following:
  • url says /catalog/bolshaya-zelenaya-shapka.html
  • using mod_rewrite, Bitrix understands that for the physical / catalog partition you always need to include the /catalog/index.php file
  • the complex component parses url, and understands that you need to connect a detailed product page, let's call it detail
  • complex component collects the parameters that are necessary for the operation of its child components
  • the complex component connects its detail.php template, inside of which the connection of children ordinary components is registered


It looks not very beautiful, but you can work. However, not everything is so simple ... If you change the parameters of a complex component using a visual editor, then the file with the address settings (urlrewrite.php) will be overwritten by the system. Moreover, if you suddenly wrote something wrong there for other pages, something will surely break without any warning. In practice, this can lead to loss of performance of entire sections of the site.
Setting the parameters of a complex component can turn into flour. One such component can easily have hundreds of input parameters, simply because you need to configure the parameters of the child components.
A complex component - it seems to be a router. However, all the routes that you create in this component will not fall into the automatically generated sitemap.xml. These links will not get into the search module. You will not have any opportunity to generate an address to the route from the outside (for example, you want to put a link to the brand’s detailed page somewhere in the sidebar, and you won’t be able to contact the router with a request to generate this URL).

Generally speaking, nobody really performs the functions of a router in Bitrix. In infoblocks, it is possible to configure the URL pattern for the infoblock page, infoblock section page, and infoblock element page. That's it, info blocks can no longer have pages.
For forums there is the ability to customize the templates of some pages. For blogs you can customize. Perhaps somewhere else you can configure something ... all this is so decentralized that putting it all together becomes quite difficult.

Conventional components are slightly simpler entities than complex components. Their task is to take a set of parameters as input, process them, feed the result of the work to the template, and cache everything and everything.
All component logic is contained in the component.php file. With the 12th version of Bitrix (now version 16 is relevant, 4 years have passed) the opportunity to "use OOP"in the components. This innovation is that instead of the component.php file, you can create a class.php file in which instead of the usual noodles you can write a class inherited from \ CBitrixComponent. And it was a big step forward, because Now you can inherit components and not use result_modifier.php at all, and not practice copy paste if you suddenly need to strongly customize the component. But here, too, everything is not so good. Of the entire set of components, only 25-30 percent can boast of having a class in their arsenal. Moreover, a good half of them simply will not give you the opportunity to expand yourself completely, because they are often written illogically.
By the way, good people are trying to standardize, somehow help developers write components, and there is an appropriate toolkit

V - View, or templates


Bitrix templates can be divided into several types:
  • Common and Complex Component 2.0 Templates
  • Website Templates
  • Templates for other entities (mail, newsletter releases, web forms, export generators, and much more)


In component templates, there is even the possibility of using template engines. In principle, any template engine can be connected, but there are no auxiliary tools out of the box. If anyone needs it, I have a couple of links to extensions for twig and blade , which work and are quite used on production. But even here the bitrixoids were perverted. The template engine can only be used with components. Connecting the template engine to the renderer of the site template, or other entities will not work, because there is no renderer there.

In templates of components the moment with their placement annoys still. The component is connected using a simple construction
$APPLICATION->IncludeComponent('bitrix:catalog.section', 'template_name', []);

The second parameter is the name of the component template. So, depending on various conditions, the location of this template may be in the most unexpected places:
  • bitrix / components / bitrix / catalog.section / templates / template_name
  • local / components / bitrix / catalog.section / templates / template_name
  • bitrix / templates / .default / components / bitrix / catalog.section / template_name
  • bitrix / templates / site_template / components / bitrix / catalog.section / template_name
  • local / templates / .default / components / bitrix / catalog.section / template_name
  • local / templates / site_template / components / bitrix / catalog.section / template_name
  • bitrix / components / bitrix / catalog / templates / .default / bitrix / catalog.section / template_name
  • local / templates / site_template / components / bitrix / catalog / .default / bitrix / catalog.section / template_name

And I haven’t listed all the options yet ...

A site template can be considered as a set of files: header.php, footer.php (yes, the site must have them), description.php (system description of the site template), template_styles.css (styles site template), a directory with component templates and a group of less significant files. And that’s all. And there is no way to influence it, nothing can be done about it. Unable to pick up the template engine.

There is nothing to say about other templates. They are either simply stored in the database as a layout with the inclusion of some “variable” data in it, or it is a dumb php file that does all the work, from selecting parameters from the database to outputting information. For example, you can look at the YML file generator for the market. There is no point in putting it here, simply because it is big enough, about 2k lines. Whoever needs it, google it, it lies in /bitrix/modules/catalog/load/yandex_run.php

File nature



As it became clear above, everything is not very good in architecture bitrix. But Bitrix has another important aspect of architecture.
Bitrix is ​​a half file CMS. A lot of things are managed using some kind of files:
  • Need a page - create a file
  • You need a set of pages - create a file and plug in a component working with infoblocks there
  • You need to set a title for the page - edit the file
  • You need to set a title for all pages of the section - create a special .section.php file in the root of this section
  • You need to edit the rights - edit the file .access.php
  • Settings before system initialization are in the dbconn.php, .settings.php and .settings_extra.php file
  • result_modifier.php, component_epilog.php, init.php, .parameters.php, .description.php ....


And there are a lot of such special files on bitrix. On the one hand, this gives some flexibility when working with the system. On the other hand, it can turn into flour for both the developer and the site manager. Page files sometimes turn into a mess from php code, layout, and plug-in components. As a result, the visual editor may incorrectly parse this file, and when editing it can easily screen php tags in some places, which will lead to the inoperability of the page. You say - do not write php code in such files? Yes I know. But Bitrix very often and no alternative makes you do this.
And in your head you need to keep constantly information about what kind of files they are and what kind of data they may contain. Different files should contain different data with a different structure, and you need to remember it for each option. To look for it in the documentation every time is hard work.

In addition to the above



You can endlessly complain about how poorly arranged in the bitrix. In my opinion, all these complaints can be characterized by one phrase - "somehow not completely." And indeed, if suddenly the Bitrixoids announce some kind of chip, then they somehow will not release it completely, do not finish it, do not bring it to mind. Examples - mass:
  • implemented ORM - did not finish, it is impossible to use fully
  • made an autoloader, it only works in modules, and not by standards
  • made it possible to connect a template engine, but it can be used not everywhere, and not completely
  • etc. etc.


In a nutshell I’ll try to characterize the other problems that I have to deal with every day.

Admin


If someone worked with the admin panel, created their pages in the administrative part as Bitrix suggests, he will understand me. This is just hell. For those who are not in the know, Bitrix suggests using a noodle file for each page. For example, the page for detailed order viewing in the admin panel by Bitrix developers takes over 4k lines. My IDE starts to slow down when viewing the contents of this file. There you and php, and js, and html. Well, at least, they got rid of SQL, although I'm sure that it is on other administrative pages.
And what prevented the administrative pages from working using the same components is not clear. There is simply no way to customize most of the administration pages. In the case of components, this could be done in two ways.
By the way, kind people made a module that will help you in building administrative pages

js framework


Bitrix has a js component that acts as a kind of client framework. None of the developers love it for several reasons:
  • it is almost not documented
  • he is monstrous
  • it duplicates in many ways the familiar jquery


Bitrix very often uses it in its components, thereby causing even more anger for developers. The core of this library in a minified form is 85kb, which is not a little. Avoid connecting it if you want to use all the features of Bitrix (composite, asset-management).

Copy-paste spirit


Recently, less and less, but still quite often, Bitrix makes something copy-paste. Do you want to modify the operation of the component - copy-paste. If you want to create your own upload template, copy and paste the system one. If you want to make almost the same template that you have - copy and paste it a little. And they even talk about it in courses for beginner developers. I have no words.

Asset Management and CDN


I really like the way in Resource Management in Bitrix. In principle, it is possible to register a set of certain “libraries”. Each library is a set of css / js files, which may depend on some other libraries. If you connect some library to the page, then before its connection all dependencies will be resolved and all dependent libraries will be inserted on the page. Everything seems to be fine, only each resource will be inserted as a separate file into the script or link tag. And thanks to this, there are sites that have 30-50 scripts connected and the same number of style files.
Shit, a question, they said in Bitrix, and made a magic checkmark that combines all these files into one. And there were sites where instead of 50 scripts there were 2, each 300-500kb. Some time ago, this association worked with errors and combined the same resources several times, but now it seems to be fixed.
And here the bitrixoids got out - they screwed the ability to upload all the resources to the CDN server. Which forever falls off ...
Then came Google Pagespeed Insights, which recommended lowering all resources to the bottom of the page. And in Bitrix, they again made a magic checkmark, which stupidly omits all resources in the body, if they are not marked with a special attribute.
And they, along with the box, distribute minified versions of their scripts, which are connected when using another magic checkmark in the admin panel.
In general, no scss, no TypeScript. If you want to properly manage resources - do not use the built-in Bitrix system, use webpack, which can be easily friends with Bitrix.

Multi-site / multi-language


This is probably the most terrible headache of the developer, which has been going on since the inception of the product. You can’t just take it and create a multilingual website. And if you need a multilingual catalog with different prices and currencies, then this turns into flour, for which you also need to pay a tidy sum (you will have to fork out for the purchase of an additional license for the next language version of the site).
If you are creating a multilingual and multicurrency site, then be prepared for the fact that Bitrix will very aggressively resist this. Multisite settings are decentralized throughout the admin panel. Each entity in the admin panel has its own dependence on the language version of the site. Some entities may not support the dependence on the site / language at all, and some have only a single binding to the language, so you have to duplicate this entity and then support it.
In the basic version, in order to make the infoblock work in several languages, you will have to create a duplicate of this infoblock. But in practice, no one does this, and tries to come up with their own ways to store one entity centrally, spreading its language-dependent attributes to other repositories.
You cannot specify the default language for localization. If you have a language variable that describes a phrase in Russian, and this language variable is not in English, then an empty line will be displayed on the English site, and you can’t influence it in any way (in many cases you could leave the Russian phrase so that there are no voids).

Rights Management Mechanism


Very sophisticated with this subsystem. It is often difficult to figure out why you granted the rights to view an entity, and the user cannot use them. For example, to give the right to edit the information block, you need to give access to the / bitrix / admin directory, issue rights for a specific information block and give rights in the main module. Too many operations need to be done to issue rights for one entity. And if there are not enough rights, then without picking the source you can’t understand why.

Configuration


There is no centralized hub in the Bitrix that would allow you to manage the system settings. Settings are again decentralized throughout the system. Options are in the module settings, in the component settings, in COption (not being moved to the admin panel). In the admin panel, options for one module can be spread out over 3-4 different pages that are located in completely different places. urlrewrite can be edited through the admin panel! Now also .settings and .settings_extra. Sometimes it is completely unclear which of them are more priority, very often there is not enough explanation for the options, the relationships are not clear. There is no native way to share configuration between developers.
Settings are very illogical. Sometimes it comes to the point of absurdity ... look at the component of the bigdata - can an unprepared person be able to adjust it?

Integration with 1C


This is the item on the list of Bitrix features that a fairly large number of customers are pecking at. Bitrix promises to configure two-way integration of the site with 1C in 2 clicks, which will instantly deliver content and documents from one system to another.
Yes, it really is, but with a few caveats.
Firstly, to make integration out of the box without additional efforts, you need to do everything exactly as it is written in the Bitrix documentation - build a directory on the site according to the rules that Bitrix offers and build a directory in 1C that requires Bitrix. Ideally, to create everything from scratch, and then maybe everything will work out of the box for you.
Secondly, Bitrix is ​​not friends with all 1C configurations out of the box. It is worth familiarizing yourself
Thirdly, there is no perfect world. Usually, a customer who wants a website already has a retail business, which means they already have 1C, which is a huge trash. And this trash has to be thrown to the site. And in order for the site to not get the same trash, you need to significantly refine the exchange mechanism.
Very often, customer requirements are very different from the vision of the product that the Bitrix team has formed, and then the development of the exchange mechanism can be quite expensive, in terms of complexity, comparable to the development of a unique exchange module for a specific case.
Therefore, there is no need to torture illusions that you will be able to easily integrate the site with 1C. This is all the machinations of marketers.

Refining exchange with 1C is also a separate issue. The class \ CIBlockCMLImport.- 5.7k lines is responsible for organizing the exchange of the directory. One of the main methods, which most often requires an extension - \ CIBlockCMLImport :: ImportElement, contains more than 1k lines. It’s enough to inherit once, update the product a couple of times for a long time, and you can get a non-working exchange with 1C. Therefore, developers often do not get into this class and try to somehow get into the import process using event handlers. Working with event handlers in bitrix, especially in the infoblock module, is also not a very pleasant task, if only because the events of the same type are not arranged uniformly, and some events are simply not enough.
In general, things are as sad as before.

Inconsistency


It sometimes seems to me that the developers of different modules do not particularly communicate with each other. Studying the source of the kernel, one comes across very heterogeneous solutions that could be implemented on the same engine, but for some reason they are implemented in different ways.
For example, you can take the properties of the elements of infoblocks and UserFields. And that and the other entity in fact is an additional field for another entity. It has a type, meaning and description. The value is stored in the separate database table (s); they have a roughly similar data access interface. So why not make them the same interface?
At the end of March, the sale module was updated to the latest version, and there they also promised arbitrary properties for orders. Is there really now a new, third interface for working with advanced entity properties?

Bitrix24


This is generally a separate topic for discussion. Confusion often arises on the basis of this system. There are 2 versions of the B24 - SaaS and Standlone. There is a marketplace for B24, but it contains applications only for the SaaS version! If you have a boxed version, bought for 200 pieces, you can’t put such popular applications as the document designer , and indeed you can’t put any application from the Bitrix24 marketplace on your Bitrix24. Here is a paradox.
Instead, a marketplace from the regular version will be available in your Bitrix24. There are many more solutions, but they are concentrated mainly around Site Management, and not B24.

Bitrix24, as I was told in the technical support department, is a holistic system. If you interfere with the work of standard system components, then be prepared that this functionality will break with subsequent updates. Bitrix will not rely on the fact that you are modifying the portal components, and this despite the fact that they officially send their customers to partners.

By the way, modifying the components in the boxed version of B24 is still a challenge. Components that generate js code, which using ajax accesses php code, which in response generates html + js. This is a hell of a mixture in which I really do not want to plunge.

Documentation


Bitrix documentation lags behind product development by 1-1.5 years. The code is very poorly covered by phpDocs, and often a comment in front of a class is exclusively “for show”, being automatically generated in the IDE.
The style of presentation of documentation in official sources is often too "free", and the contents of some articles in the documentation may not have anything to do with bitrix itself.
The course of the developer has a lot of information, but the format in which the developer is introduced to the capabilities of the system does not provide the level of perception that is required. If you go to the Symfony Cookbook, then everything is sorted out there, all the necessary aspects are painted depending on the version. Whereas in the Bitrix, the developer’s training course contains incomprehensible principles of structured information on the old and new core, which is fed first separately and then mixed, which causes a headache for beginners.

Organization of the development process


Due to the specificity of the system, it is not so easy to organize a convenient development process. Not the latest version of the Business editorial office (which was at hand) after installation takes, think about, almost 530 megabytes
$ du -s *|sort -nr|cut -f 2-|whileread a;do du -hs $a;done
523M	bitrix
204K	upload
 64K	bitrixsetup.php
 56K	desktop_app
 20K	readme.html
 20K	license.html
4,0K	web.config
4,0K	urlrewrite.php
4,0K	readme.php
4,0K	license.php
4,0K	install.config
4,0K	index.php

Of this amount, a good half are binaries and installers, which in general are not needed for version control. Generally speaking, it is customary not to version a bitrix core. Bitrix developers themselves guarantee the integrity of the kernel, manage the dependencies of the versions of different modules themselves during updates. But this immediately carries at least one big minus - it is impossible to deploy a fully working project with one command from the version control, you have to assemble it in parts: get the kernel sources from the bitrix backup, and the developers sources from git.
With the base, too, everything is not okay. If you yourself can use migrations during development, then Bitrix rolls updates to the database using ordinary scripts that you cannot control. Therefore, with updates, you still have to transfer database backups from the central development host to other developers.
Kind people, again, are sawing tools that help organize this all, but unfortunately they still have not been able to get Bitrix to follow these rules.
Officially, Bitrix allows you to have 2 copies of one distribution. One is for production, the second is for development. If you have several developers on the same project, then you are, as it were, outlawed) In fact, it’s enough to chop off the incoming and outgoing connections from / to the Bitrix machinewww.bitrixsoft.com , and then you can rivet as many copies of the development as you like, they just can’t be updated independently.

Colleagues


And the last question that I would like to touch upon.
Due to the fact that Bitrix has a low entry threshold, there are a lot of unskilled personnel among companies that provide services in this market. I had a chance to see many different projects during my career (more than a hundred in total), performed on 1C-Bitrix. I can say with confidence that 95% of them were executed "tyap-blap". Very rarely came across projects for the development of which an approach was felt, but these were units. This is all very sad.

findings


Of course, all the cons in one article can not be considered. Every day I come across some little things that daily interfere with work. But to consider all such trifles is simply impossible, and probably to nothing.

What conclusions can be drawn here. Bitrix is ​​an extremely complex system due to the fact that it has an ill-conceived architecture, many flaws that continue to live in the product for a long time. On the other hand, Bitrix is ​​a fairly simple system that requires a much lower level of qualification to start, unlike frameworks.
Supporting this product is a very thankless task compared to products such as Symfony, Laravel, Yii. The product loves to put sticks in the wheels of both inexperienced and experienced developers, which, in turn, can affect the cost of the services of experienced developers for Bitrix.

Do I regret that I spent so much time working with this system? Rather yes than no. It would be wiser to spend this time studying something more correct and more logical (which I am trying to actively do now). But it so happened that there was no one to direct me in the right direction at the beginning of my journey.

If you are a beginner php developer, then prefer Bitrix to study frameworks such as Symfony, Laravel, Yii, ZendFramework. Believe me, in the future it will more than pay off. Having mastered any of these frameworks, it will not be difficult for you in the future to develop something for Bitrix. If you have no choice, then study Bitrix, but in your free time it is better to try to plunge into the world of frameworks in order to put your brains in place.

If you are a developer with experience in Bitrix, but without experience in other frameworks, then be sure to plunge into another world, you will discover a lot of new and useful knowledge that will help you in writing much better solutions for 1C-Bitrix. Try to use solutions from other frameworks in your projects, since it’s not difficult to do this thanks to the component approach of the latter and composer.

If you are a customer, then do not trust Bitrix marketers. Nothing will be as easy as they say in bitrix presentations. And do not blame your developers for this, they have nothing to do with it. If you want to create a large and complex online store of the eldorado / mvideo / sportmaster level, then Bitrix might not be the best choice.

UPD.It can be seen that the article was read by Bitrix employees. In the section on Marketing, I wrote that in the Architecture section in the course of the Bitrix developer, marketing appeals are written. Now they are not there. Even sealed, apparently in a hurry very much. Thanks to nook for his observation and keen eye :)
image


Also popular now: