Custom ORM Model (tableProperties) - uniqcle/Bitrix GitHub Wiki

Таблицы (списки) со свойствами (полями) должны создаваться в публичной части B24. Таблицы должны быть разнесены в отдельной таблице для данного информационного блока (админ. панель).

1. Прописываем модели

а также ID инфоблоков в этих моделях.

Модели в app

local/app/Models/AbstractIblockPropertyValuesTable.php

namespace Models;

use Bitrix\Iblock\ElementTable;
use Bitrix\Iblock\PropertyEnumerationTable;
use Bitrix\Iblock\PropertyTable;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Data\Cache;
use Bitrix\Main\DB\SqlExpression;
use Bitrix\Main\Entity\ReferenceField;
use Bitrix\Main\NotImplementedException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\Entity\IntegerField;
use Bitrix\Main\Entity\StringField;
use Bitrix\Main\ORM\Data\DeleteResult;
use Bitrix\Main\ORM\Fields\DatetimeField;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\SystemException;
use CIBlockElement;

/**
 * Class AbstractIblockPropertyValueTable
 *
 * @package Models
 */
abstract class AbstractIblockPropertyValuesTable extends DataManager
{
	const IBLOCK_ID = null;

	protected static ?array $properties = null;
	protected static ?CIBlockElement $iblockElement = null;

	/**
	 * @return string
	 */
	public static function getTableName(): string
	{
		return 'b_iblock_element_prop_s'.static::IBLOCK_ID;
	}

	/**
	 * @return string
	 */
	public static function getTableNameMulti(): string
	{
		return 'b_iblock_element_prop_m'.static::IBLOCK_ID;
	}

	/**
	 * @return array
	 * @throws ArgumentException
	 * @throws SystemException
	 */
	public static function getMap(): array
	{
		$cache = Cache::createInstance();
		$cacheDir = 'iblock_property_map/'.static::IBLOCK_ID;
		$multipleValuesTableClass = static::getMultipleValuesTableClass();
		static::initMultipleValuesTableClass();

		if ($cache->initCache(3600, md5($cacheDir), $cacheDir)) {
			$map = $cache->getVars();

		} else {
			$cache->startDataCache();

			$map['IBLOCK_ELEMENT_ID'] = new IntegerField('IBLOCK_ELEMENT_ID', ['primary' => true]);
			$map['ELEMENT'] = new ReferenceField(
				'ELEMENT',
				ElementTable::class,
				['=this.IBLOCK_ELEMENT_ID' => 'ref.ID']
			);

			foreach (static::getProperties() as $property) {
				if ($property['MULTIPLE'] === 'Y') {
					$map[$property['CODE']] = new ExpressionField(
						$property['CODE'],
						sprintf('(select group_concat(`VALUE` SEPARATOR "\0") as VALUE from %s as m where m.IBLOCK_ELEMENT_ID = %s and m.IBLOCK_PROPERTY_ID = %d)',
							static::getTableNameMulti(),
							'%s',
							$property['ID']
						),
						['IBLOCK_ELEMENT_ID'],
						['fetch_data_modification' => [static::class, 'getMultipleFieldValueModifier']]
					);

					if ($property['USER_TYPE'] === 'EList') {
						$map[$property['CODE'].'_ELEMENT_NAME'] = new ExpressionField(
							$property['CODE'].'_ELEMENT_NAME',
							sprintf('(select group_concat(e.NAME SEPARATOR "\0") as VALUE from %s as m join b_iblock_element as e on m.VALUE = e.ID where m.IBLOCK_ELEMENT_ID = %s and m.IBLOCK_PROPERTY_ID = %d)',
								static::getTableNameMulti(),
								'%s',
								$property['ID']
							),
							['IBLOCK_ELEMENT_ID'],
							['fetch_data_modification' => [static::class, 'getMultipleFieldValueModifier']]
						);
					}

					$map[$property['CODE'].'|SINGLE'] = new ReferenceField(
						$property['CODE'].'|SINGLE',
						$multipleValuesTableClass,
						[
							'=this.IBLOCK_ELEMENT_ID' => 'ref.IBLOCK_ELEMENT_ID',
							'=ref.IBLOCK_PROPERTY_ID' => new SqlExpression('?i', $property['ID'])
						]
					);

					continue;
				}

				if ($property['PROPERTY_TYPE'] == PropertyTable::TYPE_NUMBER) {
					$map[$property['CODE']] = new IntegerField("PROPERTY_{$property['ID']}");
				} elseif ($property['USER_TYPE'] === 'Date') {
					$map[$property['CODE']] = new DatetimeField("PROPERTY_{$property['ID']}");
				} else {
					$map[$property['CODE']] = new StringField("PROPERTY_{$property['ID']}");
				}

				if ($property['PROPERTY_TYPE'] === 'E' && ($property['USER_TYPE'] === 'EList' || is_null($property['USER_TYPE']))) {
					$map[$property['CODE'].'_ELEMENT'] = new ReferenceField(
						$property['CODE'].'_ELEMENT',
						ElementTable::class,
						["=this.{$property['CODE']}" => 'ref.ID']
					);
				}
			}

			if (empty($map)) {
				$cache->abortDataCache();
			} else {
				$cache->endDataCache($map);
			}
		}

		return $map;
	}

	/**
	 * @param array $data
	 *
	 * @return bool
	 */
	public static function add(array $data): bool
	{
		static::$iblockElement ?? static::$iblockElement = new CIBlockElement();
		$fields = [
			'NAME'            => $data['NAME'],
			'IBLOCK_ID'       => static::IBLOCK_ID,
			'PROPERTY_VALUES' => $data,
		];

		return static::$iblockElement->Add($fields);
	}

	/**
	 * @param $primary
	 *
	 * @return DeleteResult
	 * @throws NotImplementedException
	 */
	public static function delete($primary): DeleteResult
	{
		#TODO Implement function
		throw new NotImplementedException();
	}

	/**
	 * @return array
	 * @throws ArgumentException
	 * @throws SystemException
	 * @throws ObjectPropertyException
	 */
	public static function getProperties(): array
	{
		if (isset(static::$properties[static::IBLOCK_ID])) {
			return static::$properties[static::IBLOCK_ID];
		}

		$dbResult = PropertyTable::query()
			->setSelect(['ID', 'CODE', 'PROPERTY_TYPE', 'MULTIPLE', 'NAME', 'USER_TYPE'])
			->where('IBLOCK_ID', static::IBLOCK_ID)
			->exec();
		while ($row = $dbResult->fetch()) {
			static::$properties[static::IBLOCK_ID][$row['CODE']] = $row;
		}

		return static::$properties[static::IBLOCK_ID] ?? [];
	}

	/**
	 * @param  string  $code
	 *
	 * @return int
	 * @throws ArgumentException
	 * @throws ObjectPropertyException
	 * @throws SystemException
	 */
	public static function getPropertyId(string $code): int
	{
		return (int) static::getProperties()[$code]['ID'];
	}

	/**
	 * @return array
	 */
	public static function getMultipleFieldValueModifier(): array
	{
		return [fn ($value) => array_filter(explode("\0", $value))];
	}

	/**
	 * @param  int|null  $iblockId
	 */
	public static function clearPropertyMapCache(?int $iblockId = null): void
	{
		$iblockId = $iblockId ?: static::IBLOCK_ID;
		if (empty($iblockId)) {
			return;
		}

		Cache::clearCache(true, "iblock_property_map/$iblockId");
	}

	/**
	 * @param  string  $propertyCode
	 * @param  string  $byKey
	 *
	 * @return array
	 * @throws ArgumentException
	 * @throws ObjectPropertyException
	 * @throws SystemException
	 */
	public static function getEnumPropertyOptions(string $propertyCode, string $byKey = 'ID'): array
	{
		$dbResult = PropertyEnumerationTable::getList([
			'select' => ['ID', 'VALUE', 'XML_ID', 'SORT'],
			'filter' => ['=PROPERTY.CODE' => $propertyCode, 'PROPERTY.IBLOCK_ID' => static::IBLOCK_ID],
		]);
		while ($row = $dbResult->fetch()) {
			$enumPropertyOptions[$row[$byKey]] = $row;
		}

		return $enumPropertyOptions ?? [];
	}

	/**
	 * @return string
	 */
	private static function getMultipleValuesTableClass(): string
	{
		$className = end(explode('\\', static::class));
		$namespace = str_replace('\\'.$className, '', static::class);
		$className = str_replace('Table', 'MultipleTable', $className);

		return $namespace.'\\'.$className;
	}

	/**
	 * @return void
	 */
	private static function initMultipleValuesTableClass(): void
	{
		$className = end(explode('\\', static::class));
		$namespace = str_replace('\\'.$className, '', static::class);
		$className = str_replace('Table', 'MultipleTable', $className);

		if (class_exists($namespace.'\\'.$className)) {
			return;
		}

		$iblockId = static::IBLOCK_ID;

//         $php = <<<PHP
// namespace $namespace;

// class {$className} extends \Models\AbstractIblockPropertyMultipleValuesTable
// {
//     const IBLOCK_ID = {$iblockId};
// }

// PHP;
//         eval($php);
	}
}

local/app/Models/Lists/CarsPropertyValuesTable.php

namespace Models\Lists;

use Bitrix\Main\Entity\ReferenceField;
use Models\AbstractIblockPropertyValuesTable;

class CarsPropertyValuesTable extends AbstractIblockPropertyValuesTable
{
	public const IBLOCK_ID = 22;

	public static function getMap(): array
	{
		$map = [
			'CITY' => new ReferenceField(
				'CITY',
				CarCityPropertyValuesTable::class,
				['=this.CITY_ID' => 'ref.IBLOCK_ELEMENT_ID']
			)
		];

		return parent::getMap() + $map; // TODO: Change the autogenerated stub

	}
}

local/app/Models/Lists/CarManufacturerPropertyValuesTable.php

namespace Models\Lists;

use Models\AbstractIblockPropertyValuesTable;

class CarManufacturerPropertyValuesTable extends AbstractIblockPropertyValuesTable
{
	const IBLOCK_ID = 21;
}

local/app/Models/Lists/CarCityPropertyValuesTable.php

namespace Models\Lists;

use Models\AbstractIblockPropertyValuesTable;

class CarCityPropertyValuesTable extends AbstractIblockPropertyValuesTable
{
	const IBLOCK_ID = 20;
}

local/app/Models/autoload.php

spl_autoload_register(function ($className) {
	$classPath = str_replace('\\', '/', $className);
	$file = __DIR__ . "/$classPath.php";
	//pr( $file);
	if (file_exists($file)) {
		include_once $file;
	}
});

2. Вызов в необходимых местах

С помощью getList

use Models\Lists\CarsPropertyValuesTable as CarsTable;

// вывод данных по списку записей из инфоблока Автомобили
$cars = CarsTable::getList([
	'select'=>[
		'ID'=>'IBLOCK_ELEMENT_ID',
		'NAME'=>'ELEMENT.NAME',
		'MANUFACTURER'=>'MANUFACTURER_ID'
	]
])->fetchAll();
debug($cars);


// добавление данных записей в инфоблок Автомобили
$dbResult = CarsTable::add([
	'NAME'=>'Toyota Corolla',
	'MANUFACTURER_ID'=>44,
	'CITY_ID'=>40,
	'MODEL'=>'Corolla',
	'ENGINE_VOLUME'=>'4',
	'PRODUCTION_DATE'=>date('d.m.Y H:i:s'),
]);
var_dump($dbResult);

С помощью query

use \Bitrix\Main\ObjectPropertyException,
	\Bitrix\Main\ArgumentException,
	\Bitrix\Main\SystemException;
use Models\Lists\CarsPropertyValuesTable as CarsTable;

try{

	$cars = CarsTable::query()
		->setSelect([
			'*',
			'NAME' => 'ELEMENT.NAME',
			'MARKA_NAME' => 'CUSTOM_PROP_MARKA.ELEMENT.NAME',
			'CITY_NAME' => 'CUSTOM_PROP_CITY.ELEMENT.NAME'
		])
		->setFilter(['IBLOCK_ELEMENT_ID' => 47])
		->setOrder(['NAME' => 'desc'])
		->registerRuntimeField(
			null,
			new \Bitrix\Main\Entity\ReferenceField(
				'CUSTOM_PROP_MARKA',
				\Models\Lists\CarManufacturerPropertyValuesTable::getEntity(),
				['=this.MANUFACTURER_ID' => 'ref.IBLOCK_ELEMENT_ID']
			)
		)
		->registerRuntimeField(
			null,
			new \Bitrix\Main\Entity\ReferenceField(
				'CUSTOM_PROP_CITY',
				\Models\Lists\CarCityPropertyValuesTable::getEntity(),
				['=this.CITY_ID' => 'ref.IBLOCK_ELEMENT_ID']
			)
		)
		->fetch(); //fetchAll()

} catch ( ObjectPropertyException | ArgumentException | SystemException $e){
	$errorMsg = $e -> getMessage();
	debug($errorMsg);
}
debug($cars);

Полученный результат

Array
(
    [0] => Array
        (
            [IBLOCK_ELEMENT_ID] => 49
            [MANUFACTURER_ID] => 43
            [MODEL] => Нива
            [ENGINE_VOLUME] => 1.5
            [PRODUCTION_DATE] => 2024-04-08
            [CITY_ID] => 39
            [NAME] => Лада Нива
            [MARKA_NAME] => Лада
            [CITY_NAME] => Барнаул
        )

    [1] => Array
        (
            [IBLOCK_ELEMENT_ID] => 48
            [MANUFACTURER_ID] => 44
            [MODEL] => Corolla
            [ENGINE_VOLUME] => 4
            [PRODUCTION_DATE] => 2024-04-24
            [CITY_ID] => 40
            [NAME] => Toyota Corolla
            [MARKA_NAME] => Toyota
            [CITY_NAME] => Геленджик
        )
⚠️ **GitHub.com Fallback** ⚠️