Appearance
Создание новостного раздела
Цель
Необходимо на сайте создать раздел с новостями с разделением на подразделы. На странице списка новостей необходимо отобразить:
- список с разделами
- сортировку по популярности, по названию
- фильтр по дате
- список с элементами
- пагинацию
При переходе на страницу раздела, необходимо отобразить тоже самое, что и на общей странице списка новостей, с учетом выбранного раздела.
На детальной странице необходимо отобразить:
- детальное описание
- блок с другими статьями
Реализация
Создание ИБ "Новости"
Для создания ИБ необходимо использовать модуль миграций В админ панели /bitrix/admin/rw_migration.php?lang=ru&q=create необходимо создать миграцию.
Пример миграции
php
class rw_m_1737362725_develop_create_ib_content_news extends \Realweb\Api\Module\Migration\Model\Scenario\Script
{
public static function name(): string
{
return 'create_ib_content_news';
}
public static function hash(): string
{
return '663894aca543f15851945a981d63c3bb44e9ba0c';
}
public static function priority(): string
{
return self::PRIORITY_HIGH;
}
public static function approximatelyTime(): int
{
return 1;
}
public function commit(): void
{
(new \Realweb\Api\Module\Migration\Model\Builder\Iblock())
->save('content', 'news', function (\Realweb\Api\Module\Migration\Model\Entity\Iblock $rsIblock) {
$rsIblock
->name("Новости")
->listPageUrl("#SITE_DIR#/news/")
->sectionPageUrl("#SITE_DIR#/news/#SECTION_CODE#/")
->detailPageUrl("#SITE_DIR#/news/#SECTION_CODE#/#ELEMENT_CODE#/")
->requiredCode()
->requiredSectionCode()
->textHtml()
->setIpropertyTemplates();
$obProp = $rsIblock
->saveProperty("TYPE")
->name("Тип")
->typeDropdown();
$obProp->saveEnum('Новость')->xmlId('news')->sort(10)->byDefault();
$obProp->saveEnum('Статья')->xmlId('article')->sort(20);
$rsIblock->saveElementFormInterface(0)
->setTabs(
(new \Realweb\Api\Module\Migration\Model\Entity\Option\Tab\Collection())
->addItem(
(new \Realweb\Api\Module\Migration\Model\Entity\Option\Tab\Entity('Элемент'))
->addItem('ID')
->addItem('NAME')
->addItem('CODE')
->addItem('SORT')
->addItem('PROPERTY_TYPE')
->addItem('PREVIEW_PICTURE')
->addItem('PREVIEW_TEXT')
->addItem('DETAIL_TEXT')
)
);
$rsIblock->saveSectionFormInterface(0)
->setTabs(
(new \Realweb\Api\Module\Migration\Model\Entity\Option\Tab\Collection())
->addItem(
(new \Realweb\Api\Module\Migration\Model\Entity\Option\Tab\Entity('Раздел'))
->addItem('ID')
->addItem('NAME')
->addItem('SORT')
->addItem('CODE')
)
);
});
}
public function rollback(): void
{
}
}После написания миграции необходимо ее выполнить, можно также это сделать через админ панель bitrix/admin/rw_migration.php?lang=ru&q=main
Создание модуля "Новости"
Для реализации основного функционала необходимо в рамках модуля realweb.api.module создать подмодуль news со следующей структурой.

Контроллеры
С помощью контроллеров будет возвращаться JSON-объект в ответе на эндпойнт для фронта. Подробнее про контроллеры можно прочитать тут
ListController.php
php
namespace Realweb\Api\Module\News\Controller;
use Realweb\Api\Module\News\Model\Element;
/**
* Class \Realweb\Api\Module\News\Controller\ListController
*/
class ListController extends \Realweb\Api\Controller\CollectionController
{
protected function _getDataBase(): string
{
return Element\Database::class;
}
protected function _initParams(): void
{
$this
->getPagination()
->setParam('section_id', (int)$this->getRequestParam('category_id') ?: null)
->setParam('date_from', (string)$this->getRequestParam('date_from') ?: null)
->setParam('date_to', (string)$this->getRequestParam('date_to') ?: null)
->setParam('order', (string)$this->getRequestParam('order') ?: null)
->setPageSize((int)$this->getRequestParam('limit', 12))
->setCurrentPage((int)$this->getRequestParam('page', 1));
}
/**
* @param Element\Collection $obCollection
* @return void
*/
protected function _process(\Realweb\Api\Model\Data\Collection $obCollection): void
{
$obCollection->processSections();
}
}Контроллер детальной элемента ItemController.php
php
namespace Realweb\Api\Module\News\Controller;
use Realweb\Api\Module\News\Model\Element;
/**
* Class \Realweb\Api\Module\News\Controller\ItemController
*
* @method Element\Entity getEntity()
*/
class ItemController extends \Realweb\Api\Controller\EntityController
{
public function checkGetParams(): void
{
if (!(int)$this->getRequestParam('id')) {
$this->_addErrorParams();
}
}
protected function _getDatabase(): string
{
return Element\Database::class;
}
protected function _initParams(): void
{
$this
->getPagination()
->setParam('id', (int)$this->getRequestParam('id'));
}
/**
* @param Element\Collection $obCollection
* @return void
*/
protected function _processCollection(\Realweb\Api\Model\Data\Collection $obCollection): void
{
$obCollection->processSections();
}
/**
* @param Element\Entity $obEntity
*/
protected function _processEntity(\Realweb\Api\Model\Data\Entity $obEntity): void
{
$obEntity->processMetaValues();
}
}Контроллер списка разделов CategoriesController.php
php
namespace Realweb\Api\Module\News\Controller;
use Realweb\Api\Module\News\Model\Section;
/**
* Class \Realweb\Api\Module\News\Controller\CategoriesController
*/
class CategoriesController extends \Realweb\Api\Controller\CollectionController
{
protected function _getDataBase(): string
{
return Section\Database::class;
}
protected function _initParams(): void
{
$this
->getPagination()
->setAllRecords();
}
public function get(): ?array
{
$arResult = parent::get();
if ($this->isReturnJsonData()) {
return $arResult['items'];
}
return null;
}
}Контроллер детальной раздела Section/EntityController.php
php
namespace Realweb\Api\Module\News\Controller\Section;
use Realweb\Api\Module\News\Model\Section;
/**
* Class \Realweb\Api\Module\News\Controller\Section\EntityController
*
* @method Section\Entity getEntity()
*/
class EntityController extends \Realweb\Api\Controller\EntityController
{
protected function _getDatabase(): string
{
return Section\Database::class;
}
protected function _initParams(): void
{
$this->getPagination()
->setParam('id', $this->getParamNumber('id'))
->setParam('code', $this->getParamString('code'));
}
/**
* @param Section\Entity $obEntity
*/
protected function _processEntity(\Realweb\Api\Model\Data\Entity $obEntity): void
{
$obEntity->processMetaValues();
}
}Список похожих новостей RelatedController
php
namespace Realweb\Api\Module\News\Controller;
/**
* Class \Realweb\Api\Module\News\Controller\RelatedController
*/
class RelatedController extends \Realweb\Api\Controller
{
public function checkGetParams(): void
{
if (!$this->getRequestParam('id')) {
$this->_addErrorParams();
}
}
public function get(): ?array
{
$obEntity = (new ItemController())
->setParam('id', (int)$this->getRequestParam('id'))
->getEntity();
if ($obEntity) {
$obCollection = (new ListController())
->setParam('section_id', $obEntity->getIblockSectionId())
->setParam('limit', 3)
->getCollection();
$obCollection->deleteByKey($obEntity->getId());
return array(
'items' => $obCollection->toJson()
);
}
return null;
}
}Модели
Модель предназначена для получения данных и работы с ними. Подробнее про работу с ИБ можно прочитать тут
Элемент
Коллекция элементов Element\Collection
php
namespace Realweb\Api\Module\News\Model\Element;
use Realweb\Api\Model\Main\Pagination;
use Realweb\Api\Module\News\Model\Section;
/**
* Class \Realweb\Api\Module\News\Model\Element\Collection
*
* @method Entity|null getByKey(int $iValue)
* @method Entity[] getCollection()
*/
class Collection extends \Realweb\Api\Model\Iblock\Element\Collection
{
/**
* @param Entity $obItem
* @return $this
*/
public function addItem($obItem): self
{
if (!$this->getByKey($obItem->getId())) {
$this->_collection[] = $obItem;
$this->_keys[] = $obItem->getId();
}
return $this;
}
public function processSections(): void
{
if ($this->count()) {
$arIds = array();
foreach ($this->getCollection() as $obItem) {
if ($iId = $obItem->getIblockSectionId()) {
$arIds[$iId] = $iId;
}
}
if ($arIds) {
$obCollection = Section\Database::getObjectCollection(
(new Pagination())->setParam('ids', $arIds)
);
foreach ($this->getCollection() as $obItem) {
if ($obSection = $obCollection->getByKey($obItem->getIblockSectionId())) {
$obItem->setSection($obSection);
}
}
}
}
}
}Класс для получения элементов Element\Database
php
namespace Realweb\Api\Module\News\Model\Element;
use Bitrix\Main\Type\Date;
use Realweb\Api\Model\Main\Pagination;
use Realweb\Api\Model\Iblock\Element\Query;
/**
* Class \Realweb\Api\Module\News\Model\Element\Database
*
* @method static Collection getObjectCollection(?Pagination $obNav = null)
*/
class Database extends \Realweb\Api\Model\Iblock\Element\Database
{
public static function getIblockId(): int
{
return IBLOCK_CONTENT_NEWS;
}
public static function getFields(): array
{
return array(
'ID',
'IBLOCK_ID',
'NAME',
'CODE',
'IBLOCK_SECTION_ID',
'ACTIVE_FROM',
"PREVIEW_TEXT",
"DETAIL_TEXT",
);
}
public static function getCollection(): string
{
return Collection::class;
}
public static function getEntity(): string
{
return Entity::class;
}
public static function getQuery(?Pagination $obNav = null): Query
{
$obQuery = parent::getQuery($obNav)
->setSelect(self::getFields())
->registerPreviewPicture()
->where('ACTIVE', '=', 'Y')
->addSelect('IBLOCK_SECTION.CODE', 'SECTION_CODE')
->registerDropdownField('TYPE', self::getIblockId())
->setOrder(array('SORT' => 'ASC', 'ACTIVE_FROM' => 'DESC'));
if ($obNav !== null) {
if ($strValue = $obNav->getParamString('date_from')) {
$obQuery->where('ACTIVE_FROM', '>=', new Date($strValue));
}
if ($strValue = $obNav->getParamString('date_to')) {
$obQuery->where('ACTIVE_FROM', '<=', new Date($strValue));
}
if ($strValue = $obNav->getParamString('order')) {
$arValue = explode("-", $strValue);
switch ($arValue[0]) {
case 'popular':
$obQuery->setOrder(array('SORT' => $arValue[1] == 'asc' ? 'ASC': 'DESC', 'ACTIVE_FROM' => 'DESC'));
break;
case 'name':
$obQuery->setOrder(array('NAME' => $arValue[1] == 'asc' ? 'ASC': 'DESC', 'ACTIVE_FROM' => 'DESC'));
break;
}
}
}
return $obQuery;
}
}Сущность элемента Element\Entity
php
namespace Realweb\Api\Module\News\Model\Element;
use Bitrix\Main\Type\DateTime;
use Realweb\Api\Module\News\Model\Section;
use Realweb\Api\Model\Iblock\Element\Property\Enum;
/**
* Class \Realweb\Api\Module\News\Model\Element\Entity
*
* @method DateTime getActiveFrom()
*/
class Entity extends \Realweb\Api\Model\Iblock\Element\Entity
{
private ?Section\Entity $_section = null;
private ?Enum\Entity $_type = null;
public function setData(?array $arData = null): self
{
$this->_type = new Enum\Entity(self::getByAlias($arData, 'TYPE_'));
return parent::setData($arData);
}
public function getType(): Enum\Entity
{
return $this->_type;
}
public function getSection(): ?Section\Entity
{
return $this->_section;
}
public function setSection(Section\Entity $obEntity): self
{
$this->_section = $obEntity;
return $this;
}
public function toJson(): array
{
return array(
"id" => $this->getId(),
"link" => $this->getUrl(),
"title" => $this->getName(),
"type" => $this->getType()->getValue(),
"summary" => $this->getPreviewText(),
"image" => $this->toJsonPreviewPicture(),
"date" => $this->getActiveFrom()?->format('d.m.Y, H:i'),
);
}
public function toJsonDetail(): array
{
return array(
"id" => $this->getId(),
"title" => $this->getName(),
"type" => $this->getType()->getValue(),
"summary" => $this->getDetailText(),
"date" => $this->getActiveFrom()?->format('d.m.Y, H:i'),
);
}
protected static function _getDatabase(): string
{
return Database::class;
}
}Раздел
Коллекция разделов Section\Collection
php
namespace Realweb\Api\Module\News\Model\Section;
/**
* Class \Realweb\Api\Module\News\Model\Section\Collection
*
* @method Entity|null getByKey(int $iValue)
* @method Entity[] getCollection()
*/
class Collection extends \Realweb\Api\Model\Iblock\Section\Collection
{
/**
* @param Entity $obItem
* @return $this
*/
public function addItem($obItem): self
{
if (!($this->getByKey($obItem->getId()))) {
$this->_collection[] = $obItem;
$this->_keys[] = $obItem->getId();
}
return $this;
}
}Класс для получения разделов Section\Database
php
namespace Realweb\Api\Module\News\Model\Section;
use Realweb\Api\Model\Main\Pagination;
use Realweb\Api\Model\Iblock\Section\Query;
/**
* Class \Realweb\Api\Module\News\Model\Section\Database
*/
class Database extends \Realweb\Api\Model\Iblock\Section\Database
{
public static function getCollection(): string
{
return Collection::class;
}
public static function getEntity(): string
{
return Entity::class;
}
public static function getIblockId(): int
{
return IBLOCK_CONTENT_NEWS;
}
public static function getFields(): array
{
return array(
'ID',
'IBLOCK_ID',
'NAME',
'CODE',
);
}
public static function getObjectCollection(?Pagination $obNav = null): Collection
{
return parent::getObjectCollection($obNav);
}
public static function getQuery(?Pagination $obNav = null): Query
{
$obSelect = parent::getQuery($obNav)
->setSelect(self::getFields())
->where('ACTIVE', '=', 'Y')
->setOrder(array('SORT' => 'ASC'));
if ($obNav !== null) {
}
return $obSelect;
}
}Сущность разделов Section\Entity
php
namespace Realweb\Api\Module\News\Model\Section;
/**
* Class \Realweb\Api\Module\News\Model\Section\Entity
*/
class Entity extends \Realweb\Api\Model\Iblock\Section\Entity
{
public function toJson(): array
{
return array(
'id' => (int) $this->getId(),
'link' => $this->getUrl(),
'name' => $this->getName(),
);
}
protected static function _getDatabase(): string
{
return Database::class;
}
}Настройка роутинга
Настройка роутинга осуществляется с помошью модуля роутинга. Конфигурация задается в классе \Realweb\Api\Module\Route\Model\Data.

На событии onBuild добавляется роутинг для публичной части сайта.
php
\Realweb\Api\Module\Route\Model\Helper::getInstance()
->addRoute(new Page\News\Index(SITE_DIR . 'news/'))
->addRoute(new Page\News\Section(SITE_DIR . 'news/#section_code#/'))
->addRoute(new Page\News\Element(SITE_DIR . 'news/#section_code#/#element_code#/'))Тут указываются классы, с помощью которых будет получена информация о странице.
На событии onApiBuild добавляется роутинг для АПИ методов.
php
\Realweb\Api\Module\Route\Model\Api\Helper::getInstance()
->addRoute('/news-categories/', Module\News\Controller\CategoriesController::class)
->addRoute('/news-list/', Module\News\Controller\ListController::class)
->addRoute('/news-item/', Module\News\Controller\ItemController::class)
->addRoute('/news-related/', Module\News\Controller\RelatedController::class)Тут указывается связка эндпойнтов с классами-обработчиками этих эндпойнтов. Эти эндпойнты фронт будет использовать для получения данных для страниц.
Классы для получения данных страницы
Для фронтенда данные страницы (тип страницы, метатеги, ХК и другое) получаются с помощью эндпойнта /api/entity/, в который передается текущий путь страницы path, например, список новостей /api/entity/?path/news/.
Класс для разводящей страницы
Page\News\Index
php
namespace Realweb\Api\Module\Route\Model\Page\News;
use Realweb\Api\Module\Route;
/**
* Class \Realweb\Api\Module\Route\Model\Page\News\Index
*/
class Index extends Route\Model\Page\Iblock
{
public function getCode(): string
{
return 'page-news';
}
protected function _getIblockId(): int
{
return IBLOCK_CONTENT_NEWS;
}
}Класс для страницы категории
Page\News\Section
php
namespace Realweb\Api\Module\Route\Model\Page\News;
use Realweb\Api\Module\News;
use Realweb\Api\Module\Route;
use Realweb\Api\Model\Main\Meta;
/**
* Class \Realweb\Api\Module\Route\Model\Page\News\Section
*/
class Section extends Route\Model\Page\Base
{
public function getCode(): string
{
return 'page-news-category';
}
public function getEntity(): ?News\Model\Section\Entity
{
if ($strCode = $this->getPath()->getBySlugName('section_code')?->getValue()) {
if (!array_key_exists($strCode, $this->_entity)) {
$obEntity = (new News\Controller\Section\EntityController())
->setParam('code', $strCode)
->getEntity();
if ($obEntity) {
$this->_entity[$strCode] = $obEntity;
}
}
return $this->_entity[$strCode];
}
return null;
}
public function getBreadcrumbs(): ?Route\Model\Breadcrumb\Collection
{
$obEntity = $this->getEntity();
$obCollection = Route\Model\Breadcrumb\Collection::getBase();
$obCollection->addItem(new Route\Model\Breadcrumb\Entity(array(
'link' => $obEntity->getIblock()->getUrl(),
'text' => $obEntity->getIblock()->getName(),
)));
$obCollection->addItem(new Route\Model\Breadcrumb\Entity(array(
'link' => $obEntity->getUrl(),
'text' => $obEntity->getName(),
)));
return $obCollection;
}
public function getMeta(): ?Meta\Collection
{
return $this->getEntity()->getMetaValues();
}
public function toJsonEntity(): array
{
$arResult = parent::toJsonEntity();
$arResult['id'] = $this->getEntity()->getId();
return $arResult;
}
}Класс для детальной страницы новости
Page\News\Element
php
namespace Realweb\Api\Module\Route\Model\Page\News;
use Realweb\Api\Module\News;
use Realweb\Api\Module\Route;
use Realweb\Api\Model\Main\Meta;
/**
* Class \Realweb\Api\Module\Route\Model\Page\News\Element
*/
class Element extends Route\Model\Page\Base
{
public function getCode(): string
{
return 'page-news-item';
}
public function getEntity(): ?News\Model\Element\Entity
{
if ($strCode = $this->getPath()->getBySlugName('element_code')?->getValue()) {
if (!array_key_exists($strCode, $this->_entity)) {
$obEntity = (new News\Controller\ItemController())
->setParam('code', $strCode)
->getEntity();
if ($obEntity?->getUrl() == $this->getPath()->toString()) {
$this->_entity[$strCode] = $obEntity;
}
}
return $this->_entity[$strCode];
}
return null;
}
public function getBreadcrumbs(): Route\Model\Breadcrumb\Collection
{
$obEntity = $this->getEntity();
$obCollection = Route\Model\Breadcrumb\Collection::getBase();
$obCollection->addItem(new Route\Model\Breadcrumb\Entity(array(
'link' => $obEntity->getIblock()->getUrl(),
'text' => $obEntity->getIblock()->getName(),
)));
$obCollection->addItem(new Route\Model\Breadcrumb\Entity(array(
'link' => $obEntity->getSection()->getUrl(),
'text' => $obEntity->getSection()->getName(),
)));
$obCollection->addItem(new Route\Model\Breadcrumb\Entity(array(
'link' => $obEntity->getUrl(),
'text' => $obEntity->getName(),
)));
return $obCollection;
}
public function getMeta(): ?Meta\Collection
{
return $this->getEntity()->getMetaValues();
}
public function toJsonEntity(): array
{
$arResult = parent::toJsonEntity();
$arResult['id'] = $this->getEntity()->getId();
return $arResult;
}
}