Custom ORM Model (tableProperties) - uniqcle/Bitrix GitHub Wiki
Таблицы (списки) со свойствами (полями) должны создаваться в публичной части B24. Таблицы должны быть разнесены в отдельной таблице для данного информационного блока (админ. панель).
а также 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;
}
});
С помощью 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] => Геленджик
)