Доступ к классу Categories

Чтобы получить доступ к набору категорий для com_content вы можете использовать:

    use Joomla\CMS\Categories\Categories;
    ...
    $extension = "content";   // Обратите внимание, что у вас нет обычного префикса "com_" в расширении. 
    $categories = Categories::getInstance($extension);

(На самом деле это использует устаревший API в качестве средства доступа к классу Categories, потому что он не проходит через контейнер внедрения зависимостей. Однако это намного проще, и альтернативный метод использования DIC будет описан позже). Вы можете предоставить ассоциативный массив опций в качестве второго параметра для категорий static getInstance() метод. Ключами этого массива являются:

  • "access" – если это true (или некоторое значение, соответствующее в PHP true), то будут возвращены только категории, к просмотру которых текущий пользователь имеет доступ. Если false , то будут возвращены все категории, независимо от того, имеет ли текущий пользователь доступ к их просмотру или нет. По умолчанию используется true.
  • "published" – если это значение равно 1 (целой единице), то будут возвращены только категории, состояние публикации которых равно 1 (т.е. "опубликовано"). В противном случае будут возвращены категории любого состояния. Значение по умолчанию равно 1, что означает, что будут возвращены только "опубликованные" категории.
  • "countitems" – если это значение равно 1 (целой единице), то при возврате категорий Joomla определит для каждой записи категории, сколько записей расширения связано с этой категорией (по умолчанию элементы не учитываются). Чтобы сделать это, Joomla выполнит SQL-соединение с таблицей расширений, ГДЕ поле идентификатора категории в этой таблице совпадает, и подсчитает экземпляры поля "ключ" в этой таблице. Joomla должна знать, как называются эти таблицы и поля, и они могут быть предоставлены в параметрах массива ниже.
  • "table" – имя дополнительной таблицы. Значение по умолчанию отсутствует, поэтому вы должны указать его, если используете "countitems".
  • "field" – имя поля в таблице расширений, которое содержит идентификатор категории (по умолчанию "catid").
  • "key" – имя ключевого поля в таблице расширений, по которому выполняется SQL-ПОДСЧЕТ (по умолчанию 'id').
  • "statefield" – имя поля в дополнительной таблице, которое содержит опубликованный статус записи (по умолчанию "state"). Если Joomla возвращает только опубликованные категории (как определено параметром "опубликовано", описанным выше), то она также будет учитывать только опубликованные элементы в таблице расширений. (Обратите внимание, что эквивалент НЕ выполняется для "access" – любое поле Access в таблице расширений игнорируется).

Примеры:

$categories = Categories::getInstance("content", array("access" => false, "published" => 0));

Возвращаемые узлы категории не будут ограничены доступом или опубликованным состоянием.

$categories = Categories::getInstance("helloworld", array("countitems" => 1, "table" => "helloworld", "statefield" => "published"));

Узлы категории компонента "helloworld" будут включать количество связанных записей в таблице "helloworld", где опубликованное состояние хранится в поле с именем "опубликовано".

Categories get()

Как только вы имеете экземпляр $categories вы можете получить экземпляры CategoryNode используя например

$categoryNodes = $categories->get(12);   // returns the category node for category with id=12

Метод Category get() принимает в качестве первого параметра идентификатор записи id категории, которую нужно прочитать из базы данных, и возвращает вызывающему объекту соответствующий объект CategoryNode. Если id не задан, тогда метод возвращает объект CategoryNode, относящийся к системной ROOT записи в самом верху дерева категорий в базе данных.

Функциональность get() фактически считывает из базы данных всех потомков запрошенной записи категории вплоть до root записи категории (что позволяет определить описанный ниже путь к категории), а также всех потомков запрошенной записи категории. Затем он сохраняет их локально, чтобы последующие вызовы для получения данных для любого из этих дочерних элементов могли обслуживаться путем возврата данных из этого кэша, а не выполнения еще одного запроса к базе данных.

Однако, если во второй параметр $forceload установлено значение true, затем он будет обслуживать запрос, повторно запрашивая базу данных, а не используя кэшированные записи.

На многоязычном сайте возвращаемые категории будут ограничены категориями текущего языка или language * (для всех). Аналогично, любое количество элементов в таблице расширений будет ограничено элементами текущего языка или language *, и Joomla будет предполагать, что язык хранится в поле с именем "language" (вы не можете переопределить, как называется это языковое поле).

Свойства CategoryNode

Объект из вызова метода Категории get($id) будет представлять собой объект CategoryNode, относящийся к категории с идентификатором $id. Если вы вызвали get() используя значение 'root' по умолчанию, вам нужно будет выполнить последующий вызов getChildren()⁣, чтобы получить массив объектов CategoryNode для нужного вам расширения, например:

    use Joomla\CMS\Categories\Categories;
    use Joomla\CMS\Categories\CategoryNode;

    $categories = Categories::getInstance("content");
    $rootNode = $categories->get();   
    $categoryNodes = $rootNode->getChildren();

Если у вас есть объект CategoryNode, вы можете получить доступ к свойствам этого объекта, как определено в API, и они в основном тесно связаны с атрибутами категории, видимыми в формах администрирования Joomla. Свойства, значение которых достаточно ясно, ниже не описаны, но ниже приведен список тех, значение коих может быть не совсем очевидным.

  • asset_id – если администратор определил ACL для отдельной категории, то это идентификатор записи в таблице asset, где хранятся эти ACL.
  • parent_id, lft, rgt, level – эти поля относятся к положению категории в дереве категорий, которое реализовано с использованием модели вложенного набора. Значения lft и rgt реализованы в соответствии с требованиями модели, parent_id — это идентификатор родительской записи в таблице категорий (НЕ в таблице asset – это опечатка в коде Joomla), а level равно 0 для корневого узла, 1 для тех записей, родительским элементом являющимся корневым узлом, 2 для следующего уровня потомков и т.д.
  • extension – расширение, на этот раз присутствует префикс "com_"
  • numitems – количество записей в дополнительной таблице, которые связаны с этой категорией.
  • childrennumitems – это значение не установлено – не пытайтесь его использовать!
  • slug – это то, что Joomla традиционно отображает как часть URL-адреса при отображении URL-адресов SEF. Оно имеет вид id:alias, например "3 : без категории ".
  • assets – это значение не установлено – не пытайтесь его использовать!

Метод get CategoryNode

Ряд методов CategoryNode get позволяет получить доступ к другим объектам CategoryNode в дереве:

  • getChildren(boolean $recursive = false) возвращает массив непосредственных дочерних элементов или массив всех потомков, если $recursive = true.
  • getParent() возвращает родительский CategoryNode
  • getSibling(boolean $right = true) возвращает дочерний CategoryNode вправо или влево, если $right = false.

Ряд из следующих get методов возвращают свойства объекта CategoryNode

  • getAuthor(boolean $modified_user = false) возвращает объект User, связанный с пользователем, создавшим категорию, или пользователем, который в последний раз изменял пользователя, если $modified_user = true
  • getMetadata() возвращает объект реестра Joomla, содержащий метаданные категории (структура json).
  • getParams() возвращает объект реестра Joomla, содержащий параметры категории (структура json).
  • getNumItems(boolean $recursive = false) возвращает количество записей в дополнительной таблице, которые относятся к этой категории. Если $recursive = true затем это общее количество записей, связанных с этой категорией, и всех потомков этой категории.
  • getPath() возвращает массив фрагментов, идущих от корня дерева категорий вниз по дереву к этой категории. Например, если эта категория имеет идентификатор 9 и псевдоним "собака", ее родитель имеет идентификатор 6 и псевдоним "млекопитающее", а бабушка и дедушка (непосредственно под корнем) имеют идентификатор 3 и псевдоним "животное", тогда getPath() вернется array(3 => "3:animal", 6 => "6:mammal", 9 => "9:dog").

Обратите внимание, что hasParent() включает родительский узел, являющийся корневым узлом, так что вызов этого метода всегда будет возвращать true если только вы не вызываете это на корневом узле.

Методы set CategoryNode

Существует целый ряд set методов API CategoryNode, но в основном они используются функциональностью категорий Joomla для настройки объектов CategoryNode на основе данных, извлеченных из базы данных. Вызов одного из них не сохраняет данные в базе данных, например, вы не можете использовать setParent() чтобы повторно отобразить запись категории в другой родительской категории в базе данных.

Пример кода модуля

Ниже приведен код простого модуля Joomla, который вы можете установить и запустить, чтобы продемонстрировать использование категорий и функциональности CategoryNode API. Если вы не уверены в разработке и установке модуля Joomla, то вам поможет руководство по созданию простого модуля. (Обратите внимание, что приведенный ниже код не соответствует рекомендациям по разработке модуля, но он упрощен для демонстрации API категорий).

В папке mod_sample_categories создайте следующие 2 файла:

mod_sample_categories.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="module" version="3.1" client="site" method="upgrade">
    <name>Categories demo</name>
    <version>1.0.1</version>
    <description>Code demonstrating use of Joomla APIs related to Categories</description>
    <files>
        <filename module="mod_sample_categories">mod_sample_categories.php</filename>
    </files>
</extension>

mod_sample_categories.php

<?php
defined('_JEXEC') or die('Restricted Access');

use Joomla\CMS\Factory;
use Joomla\CMS\Categories\Categories;
use Joomla\CMS\Categories\CategoryNode;

$app = Factory::getApplication();
$input = $app->input;
$ext = $input->get('categoryextension', "Content", "STRING");
$tab = $input->get('categorytable', "Content", "STRING");
echo "Getting {$ext} categories and using {$tab} table<br>";
echo "-------------<br>";

$categories = Categories::getInstance($ext, array("table" => "Content", "countItems" => 1, "access" => false));

$cat0 = $categories->get('root');

$cats = $cat0->getChildren(true);
foreach ($cats as $cat)
{
    echo "Category {$cat->id}, title: {$cat->title}<br>";
    $parent = $cat->getParent();
    echo "Level: {$cat->level}, parent id: {$cat->parent_id}, title: {$parent->title}<br>";
    echo "Numitems {$cat->getNumitems()}, including descendants: {$cat->getNumitems(true)}<br>";
    var_dump($cat->getPath());
    echo "-------------<br>";
}

$ext2 = $input->get('option', "", "STRING");
$catid = $input->get('catid', 0, "INT");
$view = $input->get('view', "", "STRING");
$id = $input->get('id', 0, "INT");
if ($ext2 && (strtolower(substr($ext2, 0, 4)) == "com_") && ($catid || (strtolower($view) == "category" && $id)))
{
    $ext2 = substr($ext2, 4);
    $categories2 = Categories::getInstance($ext2, array("access" => false));
    $categoryId = $catid ? $catid : $id; 
    echo "<br>Getting $ext2 category $categoryId<br>";
    $cat2 = $categories2->get($categoryId);
    if ($cat2)
    {
        echo "Category {$cat2->id}, title: {$cat2->title}<br>";
    }
}

Заархивируйте каталог mod_sample_categories для создания mod_sample_categories.zip.

В вашем Joomla administrator перейдите в раздел "Установить расширения" и на вкладке "Загрузить файл пакета" выберите этот zip-файл, чтобы установить этот пример модуля категорий.

Сделайте этот модуль видимым, отредактировав его (нажмите на него на странице Модулей), затем:

  1. публикуем его статус
  2. выбор позиции на странице, чтобы она была показана
  3. на вкладке назначение меню укажите страницы, на которых оно должно отображаться

Когда вы посещаете веб-страницу сайта, вы должны увидеть модуль в выбранной вами позиции, и он должен отображать набор категорий com_content в базе данных и для каждой категории:

  • идентификатор и название
  • уровень в дереве категорий, а также идентификатор и заголовок родительского элемента
  • количество статей, для которых установлена эта категория, и количество статей, для которых установлена эта категория или любой из дочерних элементов категории
  • a var_dump из категории getPath() возвращаемое значение.

Вы можете получить категории для других компонентов, добавив параметры categoryextension и categorytable к URL-адресу, например ...&categoryextension=contact&categorytable=contact_details чтобы получить com_contact Категории. Обратите внимание, что если вы пытаетесь получить категории компонента, который не является одним из основных компонентов Joomla, то вам может потребоваться указать имена полей компонента и т.д. В options к тому Categories::getInstance() звоните, как описано выше.

Код также пытается угадать, относится ли отображаемая страница к категории, проверяя, есть ли catid параметр в URL-адресе, или если view параметру присвоено значение "категория". В этом случае он показывает идентификатор и название связанной категории. Очевидно, что это может работать некорректно во всех случаях.

Получение категорий через контейнер внедрения зависимостей

В общем, предпочтительный способ получить доступ к Categories класс осуществляется через контейнер DI, и, в частности, с помощью класса CategoryFactory, который получается через контейнер DI. Как только у вас будет CategoryFactory вы можете вызвать createCategory($options) на этом экземпляре и получить обратно объект Categories; массив $options - это набор параметров, описанных вверху этой страницы. Однако объект CategoryFactory должен быть создан с расширением, для которого вы хотите получить доступ к категориям, поскольку createCategory() попытается получить доступ к классу \\<namespace prefix>\\Site\\Service\\Category для того, чтобы найти таблицу базы данных расширения и т.д.

Способ сделать это для доступа com_content категории создаются с помощью следующего в вашем файле services/provider.php.

<?php

defined('_JEXEC') or die;

use Joomla\CMS\Extension\Service\Provider\CategoryFactory;
use Joomla\CMS\Extension\Service\Provider\HelperFactory;
use Joomla\CMS\Extension\Service\Provider\Module;
use Joomla\CMS\Extension\Service\Provider\ModuleDispatcherFactory;
use Joomla\CMS\Dispatcher\ModuleDispatcherFactoryInterface;
use Joomla\CMS\Extension\ModuleInterface;
use Joomla\CMS\Helper\HelperFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Mycompany\Module\CategoriesDemo\Site\Extension\CategoriesDemoModule;
use Joomla\CMS\Categories\CategoryFactoryInterface;

return new class () implements ServiceProviderInterface {

    public function register(Container $container)
    {
        $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
        $container->registerServiceProvider(new ModuleDispatcherFactory('\\Mycompany\\Module\\CategoriesDemo'));
        $container->registerServiceProvider(new HelperFactory('\\Mycompany\\Module\\CategoriesDemo\\Site\\Helper'));
        //$container->registerServiceProvider(new Module());
        $container->set(
            ModuleInterface::class,
            function (Container $container) {
                $module = new CategoriesDemoModule(
                    $container->get(ModuleDispatcherFactoryInterface::class),
                    $container->has(HelperFactoryInterface::class) ? $container->get(HelperFactoryInterface::class) : null
                );
                $module->setCategoryFactory($container->get(CategoryFactoryInterface::class));
                return $module;
            }
        );
    }
};

Строка:

$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));

вызовет get для этой записи из контейнера DI (и, таким образом, мы получим экземпляр CategoryFactory) и сохраним ее в нашем расширении $module через вызов setCategoryFactory.

Вышесказанное предполагает, что вы устанавливаете префикс пространства имен вашего собственного модуля в файле манифеста вашего модуля:

    <namespace path="src">Mycompany\Module\CategoriesDemo</namespace>

Затем в вашем файле расширения (в файле src/Extension/CategoriesDemoModule.php вашего модуля):

<?php

namespace Mycompany\Module\CategoriesDemo\Site\Extension;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Categories\CategoryServiceInterface;
use Joomla\CMS\Categories\CategoryServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Psr\Container\ContainerInterface;
use Joomla\CMS\Extension\Module;
    
class CategoriesDemoModule extends Module implements CategoryServiceInterface, BootableExtensionInterface
{
    use CategoryServiceTrait;
    
    public static $categories;
    
    public function boot(ContainerInterface $container)
    {
        self::$categories = $this->categoryFactory->createCategory();
    }
    
    public static function getCategories()
    {
        return self::$categories;
    }
}

Это объект расширения, который создается и возвращается как $moduleв файле Services/provider.php. Функция setCategoryFactoryкоторый использовался там в CategoryServiceTrait, и эта функция сохраняет CategoryFactory как локальную переменную, доступ к которой можно получить с помощью $this->categoryFactoryили вы можете использовать:

    self::$categories = $this->getCategory();

Когда ваш модуль запустится, Joomla вызовет вашу функцию boot(), и это устанавливает статическую переменную, которая содержит экземпляр категории. Затем вы можете получить к нему доступ через эту статическую переменную, например:

    use Mycompany\Module\CategoriesDemo\Site\Extension\CategoriesDemoModule;
    // ...
    $categories = CategoriesDemoModule::$categories;

Однако обратите внимание, что

  • это более проблематично, если (как в примере кода модуля) вы динамически определяете расширение, к категориям которого вы хотите получить доступ, поскольку это влияет на вашу строку

    $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));

    когда вы загружаете запись в контейнер DI

  • если вы хотите получить доступ к категориям нескольких компонентов в ваших модулях, этот подход не работает, как если бы вы пытались загрузить оба com_contentи com_contact  класса CategoryFactory из

    $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));

    $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Contact'));

    Это связано с тем, что вторая запись перезапишет первую, поскольку они обе используют один и тот же ключ в контейнере DI.

Не все проблемы, связанные с переходом на использование DI-контейнера, решены удовлетворительно!