Skip to content

Модуль вывода списка элементов-новостей из инфоблока

Содержание


Формулировка задачи

Создать модуль вывода списка элементов с пагинацией. Вывести следующие поля:

  • Название
  • Текст анонса
  • Картинка анонса
  • Свойство типа элемента: "Новость" и/или "Блог", если есть (Заполняется в админке контент-менеджером)
  • Дата публикации (Дата начал активности в админке)
  • Ссылка на детальный просмотр элемента. Ссылка на детальную страницу строится из символьных кодов разделов + символьный код элемента

Создание контроллера

Согласно документации создадим класс контроллера /local/modules/realweb.api.module/lib/News/Controller/IndexController.php

php
<?php

namespace Realweb\Api\Module\News\Controller;

/**
 * Class \Realweb\Api\Module\News\Controller\IndexController
 */
class IndexController extends \Realweb\Api\Controller
{
	public function get(): array
	{

	}
}

Вызов метода get будет доступен по урл /api/news/.

Создание запроса в БД

Создадим запрос на получение элементов в базу данных Подробнее о создании запроса

Пусть инфоблок, в котором хранятся новости имеет символьный код news и тип content, согласно правилам формирования констант инфоблоков, ID инфоблока будет находится в константе IBLOCK_CONTENT_NEWS

В инфоблоке имеется множественное свойство типа список Тип новости TYPE_ID со значениями "Новость" и "Блог"

Согласно философии разработки MVC создадим модель для создания запроса. Предполагая, что в модуле будут присутствовать как элементы, так и разделы, заранее создадим модель в пространстве Element

php
<?php

namespace Realweb\Api\Module\News\Model\Element;

/**
 * Class \Realweb\Api\Module\News\Model\Element\Database
 */
class Database
{
	public static function getList(): array
	{
		$obQuery = \Realweb\Api\Model\Iblock\Element\Table::query()
			->setSelect(array(
				"ID", "CODE", "NAME", "PREVIEW_TEXT", "ACTIVE_FROM",
			))
			->addSelect("IBLOCK.DETAIL_PAGE_URL", "DETAIL_PAGE_URL_TEMPLATE")
			->where('IBLOCK_ID', '=', IBLOCK_CONTENT_NEWS)
			->where('ACTIVE', true)
			->registerSectionCodePath()
			->registerPreviewPicture()
			->registerMultiDropdownField("TYPE_ID",IBLOCK_CONTENT_NEWS)
		;
	}
}

В результате увидим, что если в одном элементе есть оба значения множественного поля TYPE_ID - запись задвоится. Уберем это свойство из запроса и добавим пагинацию. Для этого воспользуемся объектом навигации \Realweb\Api\Model\Main\Pagination

php
<?php

namespace Realweb\Api\Module\News\Controller;

use Realweb\Api\Module\News;

/**
 * Class \Realweb\Api\Module\News\Controller\IndexController
 */
class IndexController extends \Realweb\Api\Controller
{
	public function get(): array
	{
		$iCurrentPage = (int) $this->getRequestParam('page', 1);
		if ($iCurrentPage <= 0) {
			$iCurrentPage = 1;
		}

		$obNav = $this->getPagination();
		$obNav
                        ->setAllRecords(false)
			->setPageSize(10)
			->setCurrentPage($iCurrentPage);
		return News\Model\Database::getList($obNav);
	}
}
php
<?php

namespace Realweb\Api\Module\News\Model\Element;

use Realweb\Api\Model\Main\Pagination;

/**
 * Class \Realweb\Api\Module\News\Model\Element\Database
 */
class Database
{
	public static function getList(?Pagination $obNav = null): array
	{
		$obQuery = \Realweb\Api\Model\Iblock\Element\Table::query()
			->setSelect(array(
				"ID", "CODE", "NAME", "PREVIEW_TEXT", "ACTIVE_FROM",
			))
			->addSelect("IBLOCK.DETAIL_PAGE_URL", "DETAIL_PAGE_URL_TEMPLATE")
			->where('IBLOCK_ID', '=', IBLOCK_CONTENT_NEWS)
			->where('ACTIVE', true)
			->registerSectionCodePath()
			->registerPreviewPicture()
		;
		$rsResult = $obQuery->exec();
		$arResult = array();
		while ($arRow = $rsResult->fetch()){
			$arResult[] = $arRow;
		}

		return $arResult;
	}
}
json
{
      "ID": "6325",
      "CODE": "novost-ot-01-07-2023",
      "NAME": "Новость от 01.07.2023",
      "PREVIEW_TEXT": "Сложно сказать, почему стремящиеся вытеснить традиционное производство, нанотехнологии являются только методом политического участия и функционально разнесены на независимые элементы! Внезапно, сторонники тоталитаризма в науке являются только методом политического участия и обнародованы. Прежде всего, выбранный нами инновационный путь предопределяет высокую востребованность позиций, занимаемых участниками в отношении поставленных задач.",
      "ACTIVE_FROM": {

      },
      "DETAIL_PAGE_URL_TEMPLATE": "#SITE_DIR#/news/#SECTION_CODE_PATH#/#CODE#/",
      "SECTION_CODE_PATH": "iyul-2023",
      "PREVIEW_PICTURE_SRC": "/upload/iblock/15f/frvw1169vxcy8egvsj6ww5nfltj0vklh.png",
    }

Сущность элемента и коллекции

Как мы видим, на фронтенд отдаются лишние данные (DETAIL_PAGE_URL_TEMPLATE,SECTION_CODE_PATH ), нет детальной ссылки (она требует дополнительной обработки с заменой шаблона), нет свойства TYPE_ID, поле ACTIVE_FROM преобразовалось в пустой объект.

Как сказано в философии разработки работать с массивами не удобно и ведет к частым ошибкам (преобразование данных, получение данных из полей в итоговый результат). Создадим класс сущности записи в БД и класс коллекции (списка) таких сущностей.

В ядре realweb.api уже существует общий для элементов класс сущности \Realweb\Api\Model\Iblock\Element\Entity и коллекции \Realweb\Api\Model\Iblock\Element\Collection обладающие многими нужными для элементов методами

php
<?php

namespace Realweb\Api\Module\News\Model\Element;

/**
 * Class \Realweb\Api\Module\News\Model\Element\Entity
 * @method \Bitrix\Main\Type\DateTime getActiveFrom()
 * @mixin \Realweb\Api\Module\News\Model\Element\Entity
 */
class Entity extends \Realweb\Api\Model\Iblock\Element\Entity
{

	public function toJson(): array
	{
		return array(
			'name'=>$this->getName(),
			'previewText'=>$this->getPreviewText(),
			'image'=>$this->toJsonPreviewPicture(),
			'link'=>$this->getUrl(),
			'date'=>$this->getActiveFrom()?->format('Y-m-d'),
		);
	}

}
php
<?php

namespace Realweb\Api\Module\News\Model\Element;

/**
 * Class \Realweb\Api\Module\News\Model\Element\Collection
 */
class Collection extends \Realweb\Api\Model\Iblock\Element\Collection
{

}

Преобразуем результаты запроса в новые сущности

php
<?php

namespace Realweb\Api\Module\News\Model\Element;

use Realweb\Api\Model\Main\Pagination;

/**
 * Class \Realweb\Api\Module\News\Model\Element\Database
 */
class Database
{
	public static function getList(?Pagination $obNav = null): array
	{
		$obQuery = \Realweb\Api\Model\Iblock\Element\Table::query()
			->setSelect(array(
				"ID", "CODE", "NAME", "PREVIEW_TEXT", "ACTIVE_FROM",
			))
			->addSelect("IBLOCK.DETAIL_PAGE_URL", "DETAIL_PAGE_URL_TEMPLATE")
			->where('IBLOCK_ID', '=', IBLOCK_CONTENT_NEWS)
			->where('ACTIVE', true)
			->registerSectionCodePath()
			->registerPreviewPicture()
		;
		$rsResult = $obQuery->exec();

		$obCollection = new Collection();
		while ($arRow = $rsResult->fetch()) {
			$obCollection->addItem(new Entity($arRow));
		}

		return $obCollection->toJson();
	}
}
json
{
      "name": "Новость от 01.07.2023",
      "previewText": "Сложно сказать, почему стремящиеся вытеснить традиционное производство, нанотехнологии являются только методом политического участия и функционально разнесены на независимые элементы! Внезапно, сторонники тоталитаризма в науке являются только методом политического участия и обнародованы. Прежде всего, выбранный нами инновационный путь предопределяет высокую востребованность позиций, занимаемых участниками в отношении поставленных задач.",
      "image": {
        "src": "/upload/iblock/15f/frvw1169vxcy8egvsj6ww5nfltj0vklh.png",
        "alt": "Новость от 01.07.2023"
      },
      "link": "/news/iyul-2023/novost-ot-01-07-2023/",
      "date": "2023-07-01"
}

Теперь json выглядит намного лучше. (В итоговом результате еще не хватает типа новости)

Общая модель для элементов ИБ

Также можем заметить:

  • Модель \Realweb\Api\Module\News\Model\Element\Database не позволяет получить коллекцию элементов (только массив)
  • Операция получения элементов для ИБ новостей по сути не будет отличаться от получения элементов ИБ Контактов или ИБ Товаров кроме составления самого запроса

Для решения этих задач воспользуемся единой моделью для всех элементов ИБ \Realweb\Api\Model\Iblock\Element\Database

Для того чтобы модель "знала" с каким ИБ она работает, какие поля выбирать в запросе и какими сущностями оперирует укажем их в обязательных методах getIblockId, getCollection и getEntity

php
<?php

namespace Realweb\Api\Module\News\Model\Element;

use Realweb\Api\Model\Iblock\Element\Query;
use Realweb\Api\Model\Main\Pagination;

/**
 * Class \Realweb\Api\Module\News\Model\Element\Database
 */
class Database extends \Realweb\Api\Model\Iblock\Element\Database
{
        //Поля выборки
	public static array $arFields = array(
		"ID", "CODE", "NAME", "PREVIEW_TEXT", "ACTIVE_FROM",
	);

        //ID инфоблока, подставится в запрос автоматически
	public static function getIblockId(): int
	{
		return IBLOCK_CONTENT_NEWS;
	}

        //Сущность коллекции
	public static function getCollection(): string
	{
		return Collection::class;
	}

        //Сущность элемента
	public static function getEntity(): string
	{
		return Entity::class;
	}

        //Переопределяем метод, для того, чтобы IDE понимала с какой конкретно коллекцией мы работаем
	public static function getObjectCollection(?Pagination $obNav = null): Collection
	{
		return parent::getObjectCollection($obNav);
	}

        //Основной запрос, в моделях разных ИБ будет отличаться
	public static function getQuery(?Pagination $obNav = null): Query
	{
		$obQuery = parent::getQuery()
			->setSelect(self::getFields())
			->addSelect("IBLOCK.DETAIL_PAGE_URL", "DETAIL_PAGE_URL_TEMPLATE")
			->where('ACTIVE', true)
			->registerSectionCodePath()
			->registerPreviewPicture();

		return $obQuery;
	}
}

Теперь контроллер будет выглядеть вот так

php
<?php

namespace Realweb\Api\Module\News\Controller;

use Realweb\Api\Module\News;

/**
 * Class \Realweb\Api\Module\News\Controller\IndexController
 */
class IndexController extends \Realweb\Api\Controller
{
	public function get(): array
	{

		$iCurrentPage = (int) $this->getRequestParam('page', 1);
		if ($iCurrentPage <= 0) {
			$iCurrentPage = 1;
		}

		$obNav = $this->getPagination();
		$obNav
		        ->setAllRecords(false)
			->setPageSize(4)
			->setCurrentPage($iCurrentPage)
		;

		return News\Model\Element\Database::getObjectCollection($obNav)->toJson();
	}
}

Не хватает только свойства TYPE_ID и пагинации.

Получение множественных свойств

Для получения значений множественных свойств воспользуемся втроенным методом коллекции элементов processMultiProperties. Согласно документации для получения множественных свойств коллекция должна реализовать метод _getDatabase (для того, чтобы узнать ID инфоблока и в какой таблице хранятся значения свойств)

Заметим, что получение значений множественных свойств таким способом, как и получение свойств в основном запросе, возможно только в Инфоблоках 2.0

Объект пагинации также обладает встроеным методом toJson выводящий нужную фронтенду структуру

php
<?php

namespace Realweb\Api\Module\News\Controller;

use Realweb\Api\Module\News;

/**
 * Class \Realweb\Api\Module\News\Controller\IndexController
 */
class IndexController extends \Realweb\Api\Controller
{
	public function get(): array
	{

		$iCurrentPage = (int) $this->getRequestParam('page', 1);
		if ($iCurrentPage <= 0) {
			$iCurrentPage = 1;
		}

		$obNav = $this->getPagination();
		$obNav
		        ->setAllRecords(false)
			->setPageSize(4)
			->setCurrentPage($iCurrentPage);
		$obCollection = News\Model\Element\Database::getObjectCollection($obNav);

		//Указываем какие конкретно свойства нам нужны в аргументе
		//Если не указать - выбираются все множественные свойства
		$obCollection->processMultiProperties(array('TYPE_ID'));

		return array(
			'items' => $obCollection->toJson(),
			'pagination' => $obNav->toJson(),
		);
	}
}

После получения множественных свойств экземпляр класса новости \Realweb\Api\Module\News\Model\Element\Entity выглядит следующим образом

php
object(Realweb\Api\Module\News\Model\Element\Entity)
[
    "protected:_primary"          => int(6325)
    "protected:_exist"            => bool(true)
    "protected:_changed"          => bool(false)
    "protected:_data"             =>  array(8)
        [
            "ID"                       => int(6325)
            "CODE"                     => string(20) "novost-ot-01-07-2023"
            "NAME"                     => string(30) "Новость от 01.07.2023"
            "PREVIEW_TEXT"             => string(826) "Сложно сказать, почему стремящиеся вытеснить традиционное производство, нанотехнологии являются только методом политического участия и функционально разнесены на независимые элементы! Внезапно, сторонники тоталитаризма в науке являются только методом политического участия и обнародованы. Прежде всего, выбранный нами инновационный путь предопределяет высокую востребованность позиций, занимаемых участниками в отношении поставленных задач."
            "ACTIVE_FROM"              =>  object(Bitrix\Main\Type\DateTime)
            "DETAIL_PAGE_URL_TEMPLATE" => string(43) "#SITE_DIR#/news/#SECTION_CODE_PATH#/#CODE#/"
            "SECTION_CODE_PATH"        => string(9) "iyul-2023"
            "PREVIEW_PICTURE_SRC"      => string(55) "/upload/iblock/15f/frvw1169vxcy8egvsj6ww5nfltj0vklh.png"
        ]
    "protected:_orm_result"       => NULL
    "protected:_saved"            => bool(false)
    "protected:_refresh_data"     => bool(true)
    "protected:_url_template"     => NULL
    "protected:_meta_class"       => NULL
    "protected:_meta_values"      => NULL
    "protected:_iblock"           => NULL
    "protected:_preview_picture"  => NULL
    "protected:_detail_picture"   => NULL
    "protected:_ciblock_element"  => NULL
    "protected:_multi_properties" =>  array(1)
        [
            "TYPE_ID" =>  object(Realweb\Api\Model\Iblock\Element\Property\Enum\Collection)
                [
                    "protected:_collection" =>  array(2)
                        [
                            0 =>  object(Realweb\Api\Model\Iblock\Element\Property\Enum\Entity)
                            1 =>  object(Realweb\Api\Model\Iblock\Element\Property\Enum\Entity)
                        ]
                    "protected:_keys"       =>  array(2)
                    "protected:_xml_id"     =>  array(2)
                ]
        ]
]

В свойстве _multi_properties появилась коллекция значений свойства TYPE_ID. Добавим их в итоговый json методом toJsonType

php
<?php

namespace Realweb\Api\Module\News\Model\Element;

/**
 * Class \Realweb\Api\Module\News\Model\Element\Entity
 *
 * @method \Bitrix\Main\Type\DateTime getActiveFrom()
 *
 * @mixin \Realweb\Api\Module\News\Model\Element\Entity
 */
class Entity extends \Realweb\Api\Model\Iblock\Element\Entity
{
	public function toJson(): array
	{
		return array(
			'name' => $this->getName(),
			'previewText' => $this->getPreviewText(),
			'image' => $this->toJsonPreviewPicture(),
			'link' => $this->getUrl(),
			'date' => $this->getActiveFrom()?->format('Y-m-d'),
			'types' => $this->toJsonType(),
		);
	}

	public function toJsonType(): array
	{
		$arResult = array();
		if ($obTypeValues = $this->getMultiProperty('TYPE_ID')) {
			foreach ($obTypeValues->getCollection() as $obTypeValue) {
				$arResult[] = $obTypeValue->getValue();
			}
		}
		return $arResult;
	}

}

Итоговый результат

json
"items": [
      {
        "name": "Новость от 01.07.2023",
        "previewText": "Сложно сказать, почему стремящиеся вытеснить традиционное производство, нанотехнологии являются только методом политического участия и функционально разнесены на независимые элементы! Внезапно, сторонники тоталитаризма в науке являются только методом политического участия и обнародованы. Прежде всего, выбранный нами инновационный путь предопределяет высокую востребованность позиций, занимаемых участниками в отношении поставленных задач.",
        "image": {
          "src": "/upload/iblock/15f/frvw1169vxcy8egvsj6ww5nfltj0vklh.png",
          "alt": "Новость от 01.07.2023"
        },
        "link": "/news/iyul-2023/novost-ot-01-07-2023/",
        "date": "2023-07-01",
        "types": [
          "Новость",
          "Блог"
        ]
      },
      {
        "name": "Блог от 07.07.2023",
        "previewText": "А ещё базовые сценарии поведения пользователей являются только методом политического участия и объявлены нарушающими общечеловеческие нормы этики и морали. С другой стороны, высокое качество позиционных исследований позволяет оценить значение как самодостаточных, так и внешне зависимых концептуальных решений. Современные технологии достигли такого уровня, что начало повседневной работы по формированию позиции предполагает независимые способы реализации модели развития!",
        "image": {
          "src": "/upload/iblock/1d9/sf3ah97d22v2rkon2bnln9iqb85ih008.png",
          "alt": "Блог от 07.07.2023"
        },
        "link": "/news/iyul-2023/blog-ot-07-07-2023/",
        "date": "2023-07-07",
        "types": [
          "Блог"
        ]
      },
      {
        "name": "Запись от 24.07.2023",
        "previewText": "Есть над чем задуматься: некоторые особенности внутренней политики подвергнуты целой серии независимых исследований. Наше дело не так однозначно, как может показаться: базовый вектор развития влечет за собой процесс внедрения и модернизации новых принципов формирования материально-технической и кадровой базы. Высокий уровень вовлечения представителей целевой аудитории является четким доказательством простого факта: выбранный нами инновационный путь представляет собой интересный эксперимент проверки системы обучения кадров, соответствующей насущным потребностям.",
        "image": {
          "src": "/upload/iblock/15f/frvw1169vxcy8egvsj6ww5nfltj0vklh.png",
          "alt": "Запись от 24.07.2023"
        },
        "link": "/news/iyul-2023/zapis-ot-24-07-2023/",
        "date": "2023-07-24",
        "types": [
          "Новость",
          "Блог"
        ]
      },
      {
        "name": "Новость от 02.08.2023",
        "previewText": "Картельные сговоры не допускают ситуации, при которой непосредственные участники технического прогресса своевременно верифицированы. Приятно, граждане, наблюдать, как акционеры крупнейших компаний являются только методом политического участия и обнародованы. Внезапно, непосредственные участники технического прогресса, инициированные исключительно синтетически, функционально разнесены на независимые элементы.",
        "image": {
          "src": "/upload/iblock/33c/mbsm0j8qplp0kszwd76v80zvi6zakt9l.png",
          "alt": "Новость от 02.08.2023"
        },
        "link": "/news/avgust-2023/novost-ot-02-08-2023/",
        "date": "2023-08-02",
        "types": [
          "Новость"
        ]
      }
    ],
    "pagination": {
      "page": 1,
      "total": 46,
      "limit": 4
    }

Альтернативой методу $obTypeValues = $this->getMultiProperty('TYPE_ID') может быть использование магического метода getTypeId(), который вернет коллекцию \Realweb\Api\Model\Iblock\Element\Property\Enum\Collection

В названии метода символ "_" разделяется в стиле camelCase

Кеширование

У нас получилось выполнить задачу, но при каждом обращении к контроллеру (читай при каждой загрузке страницы) мы постоянно будем обращаться к БД за данными. Для предотвращения этого закешируем данные.

Для этого воспользуемся классом кеша \Realweb\Api\Model\Main\Cache

php
<?php

namespace Realweb\Api\Module\News\Controller;

use Realweb\Api\Model\Main\Cache;
use Realweb\Api\Model\Main\Pagination;
use Realweb\Api\Module\News;

/**
 * Class \Realweb\Api\Module\News\Controller\IndexController
 */
class IndexController extends \Realweb\Api\Controller
{
	public function get(): array
	{

		$iCurrentPage = (int) $this->getRequestParam('page', 1);
		if ($iCurrentPage <= 0) {
			$iCurrentPage = 1;
		}

		$obNav = $this->getPagination();
		$obNav
		        ->setAllRecords(false)
			->setPageSize(4)
			->setCurrentPage($iCurrentPage)
		;

		$arCacheResult = Cache::getInstance()
			//Установка уникального ID кеша, объект $obNav сериализуется и хешируется
			->setId($obNav)
			//Установка директории хранения кеша
			->setDir(__CLASS__)
			//Добавление тега кеша для сброса, как только произойдут изменения в ИБ - кеш сбросится
			->addTag(News\Model\Element\Database::getCacheTag())
			//Вызов коллбека, возвращающий данные
			->get(fn () => $this->_getList($obNav))
		;

		$obCollection = $arCacheResult['collection'];

		return array(
			'items' => $obCollection->toJson(),
			'pagination' => $arCacheResult['pagination'],
		);
	}

	private function _getList(Pagination $obNav)
	{
		$obCollection = News\Model\Element\Database::getObjectCollection($obNav);
		$obCollection->processMultiProperties(array('TYPE_ID'));

		//Кешировать будем саму коллекцию
		return array(
			'collection' => $obCollection,
			'pagination' => $obNav->toJson(),
		);
	}
}

Именование контроллера

На данный момент наш контроллер называется \Realweb\Api\Module\News\Controller\IndexController, что не очень удобно - из названия не понятно, что же делает на самом деле этот контроллер. В модуле могут появиться контроллеры для получения разделов, свойств, создания и удаления новости. Т.к данный контроллер получает коллекцию элементов то так и назовем его \Realweb\Api\Module\News\Controller\Element\CollectionController. Согласно документации вызов контроллера будет доступен по урл /api/news-element-collection/.

Для того чтобы можно было получить данные по другому урл можно воспользоваться функционалом алиасов

Объединение контроллеров

В рамках одного проекта потребуется выводить разные данные - списки элементов и разделов разных ИБ. По сути функциональность большинства контроллеров сводится к следующему:

  1. Создать объект Пагинации/Фильтра и установить параметры из запроса
  2. Вызвать метод получения коллекции соответствующей модели
  3. Опционально провести некоторые манипуляции с коллекцией
  4. Закешировать результат
  5. Отдать результат в браузер методом toJson

Попробуем объединить данную функциональность в один контроллер, учитывая следующие особенности

  • Каждый контроллер имеет свою модель
  • Каждый контроллер имеет свою обработку и установку входящих параметров
  • Каждый контроллер может иметь свои манипуляции с коллекцией
php
<?php

namespace Realweb\Api\Controller;

use Realweb\Api\Controller;
use Realweb\Api\Model\Data\Collection;
use Realweb\Api\Model\Data\Database;
use Realweb\Api\Model\Main\Cache;
use Realweb\Api\Model\Main\Pagination;

/**
 * Class \Realweb\Api\Controller\CollectionController
 */
abstract class CollectionController extends Controller
{
	/**
	 * Для каждого контроллера будет своя модель
	 * @return Database
	 */
	abstract protected function _getDatabase(): string;

	//Объект коллекции для всех общий, удобно получать данные на бекенде
	protected ?Collection $_collection = null;

	//Метод получения данных для бекенда
	public function getCollection(): ?Collection
	{
		if ($this->_collection === null) {
			$this->setReturnJsonData(false);
			$this->get();
		}

		return $this->_collection;
	}

	//Основной метод получения данных общий для всех
	public function get(): ?array
	{
		$this->_initParams();
		$obCache = Cache::getInstance()
			->setId($this->getPagination())
			//Время кеширования также может отличаться
			->setTime($this->_getCacheTime())
			->setDir(__CLASS__)
		;
		//Теги для сброса кеша разные у всех, по умолчанию тег инфоблока, но можно и переопределить, если требуется
		if ($mTag = $this->_getCacheTag()) {
			if (is_array($mTag)) {
				foreach ($mTag as $strTag) {
					$obCache->addTag($strTag);
				}
			} else {
				$obCache->addTag($mTag);
			}
		}
		//Флаг, который позволяет не кешировать данные
		if ($this->getParamBool('without_cache')) {
			$obCache->setTime(0);
		}

		//Получение закешированного результата
		$arCacheResult = $obCache->get(fn () => $this->_getList($this->getPagination()));
		$obCollection = $arCacheResult['collection'];
		$this->_setCollection($obCollection);

		//Если требуется отдать данные в браузер - отдаем
		if ($this->isReturnJsonData()) {
			return array(
				'items' => $obCollection->toJson(),
				'pagination' => $arCacheResult['pagination'],
			);
		}

		return null;

	}

	//Основной запрос данных из модели
	protected function _getList(Pagination $obNav): array
	{
		$obCollection = $this->_getDatabase()::getObjectCollection($obNav);

		//Если нужны какие-то манипуляции с коллекцией - делаем их
		$this->_process($obCollection);

		return array(
			'collection' => $obCollection,
			'pagination' => $obNav->toJson(),
		);
	}

	//Установка коллекции в экземпляр класса контроллера
	protected function _setCollection(Collection $obCollection): void
	{
		$this->_collection = $obCollection;
	}

	//Инициализация параметров, можно переопределить в контроллере проекта
	protected function _initParams(): void
	{
		$this->getPagination();
	}

	protected function _process(Collection $obCollection): void
	{
	}

	protected function _getCacheTag(): array | string | null
	{
		return $this->_getDatabase()::getCacheTag();
	}

	protected function _getCacheTime(): int
	{
		return Cache::DURATION;
	}
}

Таким образом контроллер получения новостей принимает вид:

php
<?php

namespace Realweb\Api\Module\News\Controller\Element;

use Realweb\Api\Module\News\Model\Element;
use Realweb\Api\Controller;

/**
 * Class \Realweb\Api\Module\News\Controller\Element\CollectionController
 */
class CollectionController extends Controller\CollectionController
{
	/**
	 * Метод необходим для того, чтобы IDE понимала с какой коллекцией мы работаем
	 * @return Element\Collection
	 */
	public function getCollection(): \Realweb\Api\Model\Data\Collection
	{
		return parent::getCollection();
	}

	//Возвращения класса модели
	protected function _getDataBase(): string
	{
		return Element\Database::class;
	}

	//Инициализация параметров, установка пагинации
	public function _initParams(): void
	{
		parent::_initParams();
		$iCurrentPage = (int) $this->getRequestParam('page', 1);
		if ($iCurrentPage <= 0) {
			$iCurrentPage = 1;
		}

		$this->getPagination()
		        ->setAllRecords(false)
			->setPageSize(4)
			->setCurrentPage($iCurrentPage);
	}

	/**
	 * Получение множественных свойств
	 * @param Element\Collection $obCollection
	 */
	protected function _process(\Realweb\Api\Model\Data\Collection $obCollection): void
	{
		$obCollection->processMultiProperties(array("TYPE_ID"));
	}
}

Контроллер CollectionController уже создан в ядре realweb.api

Метод getCollection необходим для получения коллекции элементов на бекенде, например

php
use Realweb\Api\Module\News;
$obCollection = (new News\Controller\Element\CollectionController())->getCollection();
//Теперь можно работать с коллекцией элементов

Пример кода

Пример кода можно найти по ссылке