База данных

Подробное обсуждение дизайна и мотивации модели данных Elgg.

Обзор

В Elgg всё работает на единой модели данных, основанной на атомарных единицах данных, называемых сущностями.

Плагинам не рекомендуется напрямую взаимодействовать с базой данных, что создаёт более стабильную систему и лучший пользовательский опыт, поскольку контент, созданный разными плагинами, может смешиваться согласованным образом. При таком подходе плагины разрабатываются быстрее и в то же время становятся гораздо более мощными.

Каждая сущность в системе наследует класс ElggEntity. Этот класс управляет правами доступа, владением, контейнерами и предоставляет согласованный API для доступа и обновления свойств сущности.

Вы можете расширить сущности дополнительной информацией двумя способами:

Metadata: Эта информация описывает сущность, она обычно

добавляется автором сущности при создании или обновлении сущности. Примеры метаданных включают теги, номер ISBN или сторонний ID, местоположение, геокоординаты и т.д. Представляйте метаданные как простое хранилище ключ-значение.

Annotations: Эта информация расширяет сущность свойствами, обычно

добавляемыми третьей стороной. Такие свойства включают рейтинги, лайки и голоса.

Основные различия между метаданными и аннотациями:

  • метаданные не имеют владельцев, а аннотации имеют

  • метаданные не контролируются доступом, а аннотации контролируются

  • метаданные предзагружаются при создании сущности, а аннотации загружаются только по требованию

Эти различия могут иметь последствия для производительности и вашей бизнес-логики, поэтому тщательно подумайте, как вы хотите прикреплять данные к вашим сущностям.

В некоторых случаях может быть полезно избежать использования метаданных и аннотаций и вместо этого создать новые сущности, прикрепив их через container_guid или отношение.

Модель данных

Диаграмма модели данных Elgg

Диаграмма модели данных Elgg

Сущности

ElggEntity — базовый класс модели данных Elgg, поддерживающий общий набор свойств и методов.

  • Числовой глобально уникальный идентификатор (см. GUIDs)

  • Права доступа. (Когда плагин запрашивает данные, он никогда не получает доступ к данным, которые текущий пользователь не имеет права видеть)

  • Произвольный подтип (подробнее ниже)

  • Владелец

  • Контейнер, используемый для связывания контента с группой или пользователем

  • UNIX-метки времени для определённых действий:
    • Когда была создана сущность

    • Когда сущность была последний раз обновлена

    • Когда сущность выполнила своё последнее действие или над ней было совершено действие

    • Когда сущность была удалена

  • Состояние удаления (удалённые сущности не отображаются в обычных обстоятельствах)

  • Состояние отключения (отключённые сущности не отображаются в обычных обстоятельствах)

Типы

Фактические сущности будут экземплярами четырёх различных подклассов, каждый из которых имеет отдельное свойство type и свои дополнительные свойства и методы.

Тип

PHP-класс

Представляет

object

ElggObject

Большая часть контента, созданного пользователями, например посты блога, загрузки и закладки.

group

ElggGroup

Организованная группа пользователей со своей страницей профиля

user

ElggUser

Пользователь системы

site

ElggSite

Сайт, обслуживаемый установкой Elgg

Каждый тип имеет свой расширенный API. Например, пользователи могут быть друзьями с другими пользователями, группы могут иметь участников, а объекты могут получать лайки и комментарии.

Подтипы

Каждая сущность должна определять подтип, который плагины используют для дальнейшей специализации сущности. Elgg упрощает запрос конкретных сущностей заданного подтипа(ов), а также назначение им специального поведения и представлений.

Подтипы чаще всего присваиваются экземплярам ElggEntity для обозначения типа созданного контента. Например, плагин блога создаёт объекты с подтипом "blog".

По умолчанию пользователи, группы и сайты имеют подтипы user, group и site соответственно.

Плагины могут использовать пользовательские классы сущностей, расширяющие базовый класс типа. Для этого им нужно зарегистрировать свой класс во время выполнения (например, в обработчике 'init', 'system'), используя elgg_set_entity_class(). Например, плагин блога может использовать elgg_set_entity_class('object', 'blog', \ElggBlog::class).

Плагины могут использовать elgg-plugin.php для определения класса сущности через параметр entities.

Особенности подтипов

Перед вызовом метода save() сущности подтип должен быть установлен путём записи строки в свойство subtype.

Предупреждение

Подтип нельзя изменить после сохранения.

GUIDs

GUID — это целое число, которое однозначно идентифицирует каждую сущность в установке Elgg (глобально уникальный идентификатор). Он назначается автоматически при первом сохранении сущности и никогда не может быть изменён.

Некоторые функции API Elgg работают с GUID вместо объектов ElggEntity.

Состояние удаления

Начиная с Elgg 6.0 сущности также имеют состояние удаления. Когда заданный тип/подтип сущности поддерживает это, перед удалением из базы данных она может получить состояние удаления. Таким образом пользователь может восстановить сущность, если действие удаления было выполнено слишком поспешно. Например, пользователь удаляет пост блога, но этого не следовало делать. Теперь у пользователя есть возможность восстановить блог в его исходном состоянии без необходимости переписывать его.

В базе данных это управляется столбцом deleted в таблице entities, который может иметь значение yes или no (по умолчанию), и столбцом time_deleted, который хранит метку времени UNIX, когда сущность была удалена.

Администратор сайта может установить период хранения для удалённых элементов. После истечения периода хранения сущность будет навсегда удалена из базы данных.

Удалённые элементы не будут отображаться в обычных случаях использования. В примере с постом блога блог не будет отображаться в списке блогов, и если кто-то сохранил ссылку на пост блога, страница вернёт ошибку 404 - Not Found.

В разделе настроек пользователя есть специальная страница, где можно просмотреть все удалённые сущности пользователя. Здесь пользователь имеет возможность восстановить сущность или навсегда удалить её до истечения периода хранения.

Эта специальная страница также доступна владельцам групп для удалённых сущностей в их группе.

См.также

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

ElggObject

Тип сущности ElggObject представляет произвольный контент в установке Elgg, например посты блога, загруженные файлы и т.д.

Помимо стандартных свойств ElggEntity, ElggObject также поддерживает:

  • title — заголовок объекта (текст с экранированием HTML)

  • description — описание объекта (HTML)

Большинство других данных об объекте обычно хранится через метаданные.

Примечание

Рекомендуется зарегистрировать пользовательское расширение ElggObject для вашего подтипа (например, blog, file и т.д.). Там вы можете установить подтип, чтобы он всегда был вашим подтипом, при необходимости настроить поля формы по умолчанию и легко создать другие вспомогательные функции.

ElggUser

Тип сущности ElggUser представляет пользователей в установке Elgg. Они будут установлены как отключённые, пока их аккаунты не будут активированы (если только они не были созданы из панели администратора).

Помимо стандартных свойств ElggEntity, ElggUser также поддерживает:

  • name — имя пользователя в виде простого текста, например «Hugh Jackman»

  • username — их имя для входа, например «hjackman»

  • password — хешированная версия их пароля

  • email — их адрес электронной почты

  • language — их код языка по умолчанию.

  • prev_last_action — предыдущее значение last_action

  • last_login — метка времени UNIX их последнего входа в систему

  • prev_last_login — предыдущее значение last_login

ElggSite

Тип сущности ElggSite представляет вашу установку Elgg (через URL вашего сайта).

Помимо стандартных свойств ElggEntity, ElggSite также поддерживает:

  • name — название сайта

  • description — описание сайта

  • url — адрес сайта

ElggGroup

Тип сущности ElggGroup представляет объединение пользователей Elgg. Пользователи могут присоединяться к группам, покидать их и публиковать контент в группах.

Помимо стандартных свойств ElggEntity, ElggGroup также поддерживает:

  • name — название группы (текст с экранированием HTML)

  • description — описание группы (HTML)

ElggGroup имеет дополнительные методы для управления контентом и членством.

Плагин Groups

Не следует путать с типом сущности ElggGroup, Elgg поставляется с плагином под названием «Groups», который предоставляет стандартный UI/UX для взаимодействия пользователей сайта с группами. Каждой группе предоставляется страница профиля, связывающая пользователей с контентом внутри группы.

Вы можете изменить пользовательский опыт с помощью традиционных средств расширения плагинов или полностью заменить плагин Groups на свой собственный.

Несколько основных плагинов Elgg предлагают поддержку группового контента, такого как блоги, закладки, обсуждения, файлы и страницы.

Написание плагина с поддержкой групп

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

Добавление контента

Передавая группу как container_guid через скрытое поле ввода, вы можете использовать одну форму и одно действие для добавления как пользовательского, так и группового контента.

Используйте ElggEntity->canWriteToContainer(0, $type, $subtype), чтобы определить, имеет ли текущий пользователь право добавлять контент в группу.

Имейте в виду, что вам нужно будет передать GUID контейнера или имя пользователя на страницу, ответственную за публикацию, и соответствующее значение, чтобы это можно было сохранить в вашей форме как скрытое поле ввода для удобной передачи в ваши действия. В рамках действия «create» вам нужно будет принять это поле ввода и сохранить его как свойство вашего нового элемента (по умолчанию — контейнер текущего пользователя):

$user = elgg_get_logged_in_user_entity();
$container_guid = (int) get_input('container_guid');

if ($container_guid) {
    $container = get_entity($container_guid);

    if (!$container instanceof \ElggEntity || !$container->canWriteToContainer($user->guid, 'object', 'my_content_subtype')) {
        return elgg_error_response(elgg_echo('actionunauthorized'));
    }
} else {
    $container_guid = elgg_get_logged_in_user_guid();
}

$object = new YourObjectClass();
$object->container_guid = $container_guid;

...

// redirect to the created object
return elgg_ok_response('', $object->getURL());

Владение

Сущности имеют свойство GUID owner_guid, которое определяет их владельца. Обычно это относится к GUID пользователя, хотя сайты и сами пользователи часто не имеют владельца (значение 0).

Владение сущностью частично определяет, можете ли вы получить доступ к этой сущности или редактировать её.

Контейнеры

Для удобного поиска контента по группе или по пользователю контент обычно устанавливается как «содержащийся» либо пользователем, который его опубликовал, либо группой, в которую пользователь опубликовал. Это означает, что свойство container_guid нового объекта будет установлено в GUID текущего ElggUser или целевого ElggGroup.

Например, три поста блога могут принадлежать разным авторам, но все они могут содержаться в группе, в которую они были опубликованы.

Примечание

Это не всегда так. Сущности комментариев содержатся в объекте, на который оставлен комментарий, а в некоторых сторонних плагинах контейнер может использоваться для моделирования родительско-дочерней связи между сущностями (например, объект «папка», содержащий объект файла).

Аннотации

Аннотации — это фрагменты данных, прикреплённые к сущности, которые позволяют пользователям оставлять рейтинги или другую соответствующую обратную связь. Плагин опросов может регистрировать голоса как аннотации.

Аннотации хранятся как экземпляры класса ElggAnnotation.

Каждая аннотация имеет:

  • Внутренний тип аннотации (например, comment)

  • Значение (которое может быть строкой, булевым значением или целым числом)

  • Право доступа, отличное от сущности, к которой она прикреплена

  • Владелец

Добавление аннотации

Самый простой способ добавить аннотацию — использовать метод annotate на сущности, который определён как:

public function annotate(
    $name,           // The name of the annotation type (eg 'comment')
    $value,          // The value of the annotation
    $access_id = 0,  // The access level of the annotation
    $owner_id = 0,   // The annotation owner, defaults to current user
    $vartype = ""    // 'text', 'bool' or 'integer'
)

Например, чтобы оставить рейтинг на сущности, вы можете вызвать:

$entity->annotate('rating', $rating_value, $entity->access_id);

Чтение аннотаций

Для получения аннотаций на объекте вы можете вызвать следующий метод:

$annotations = $entity->getAnnotations(
    $name,    // The type of annotation
    $limit,   // The number to return
    $offset,  // Any indexing offset
    $order,   // 'asc' or 'desc' (default 'asc')
);

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

$averagevalue = $entity->getAnnotationsAvg($name);  // Get the average value
$total = $entity->getAnnotationsSum($name);         // Get the total value
$minvalue = $entity->getAnnotationsMin($name);      // Get the minimum value
$maxvalue = $entity->getAnnotationsMax($name);      // Get the maximum value

Полезные вспомогательные функции

Комментарии

Если вы хотите предоставить функциональность комментариев для объектов вашего плагина, следующая функция предоставит полный список, форму и действия:

function elgg_view_comments(ElggEntity $entity)

Метаданные

Метаданные в Elgg позволяют хранить дополнительные данные в ElggEntity помимо встроенных полей, которые поддерживает эта сущность. Например, ElggObjects поддерживают только базовые поля сущности плюс заголовок и описание, но вы можете захотеть включить теги или номер ISBN. Аналогично, вы можете захотеть, чтобы пользователи могли сохранять дату рождения.

Под капотом метаданные хранятся как экземпляр класса ElggMetadata, но на практике вам не нужно об этом беспокоиться (хотя если вам интересно, см. справку по классу ElggMetadata). Что вам нужно знать:

  • Вы потенциально можете иметь несколько элементов каждого типа метаданных, прикреплённых к одной сущности

  • Как и аннотации, значения хранятся как строки, булевы значения или целые числа

  • Имя метаданных чувствительно к регистру

Простой случай

Добавление метаданных

Чтобы добавить фрагмент метаданных к сущности, просто вызовите:

$entity->metadata_name = $metadata_value;

Например, чтобы добавить дату рождения пользователю:

$user->dob = $dob_timestamp;

Или чтобы добавить пару тегов к объекту:

$object->tags = array('tag one', 'tag two', 'tag three');

При добавлении метаданных таким образом:

  • Переназначение фрагмента метаданных перезапишет старое значение

Это подходит для большинства целей. Будьте внимательны, чтобы отметить, какие атрибуты являются метаданными, а какие встроены в тип сущности, с которым вы работаете. Вам не нужно сохранять сущность после добавления или обновления метаданных. Вам нужно сохранить сущность, если вы изменили один из её встроенных атрибутов. Например, если вы изменили access_id объекта ElggObject, вам нужно сохранить его, иначе изменение не будет отправлено в базу данных.

Чтение метаданных

Для получения метаданных относитесь к ним как к свойству сущности:

$tags_value = $object->tags;

Обратите внимание, что это вернёт абсолютное значение метаданных. Чтобы получить метаданные как объект ElggMetadata, вам нужно будет использовать методы, описанные в разделе более тонкое управление ниже.

Если вы сохранили несколько значений в этом фрагменте метаданных (как в примере с «тегами» выше), вы получите массив всех этих значений. Если вы сохранили только одно значение, вы получите строку, булево значение или целое число. Сохранение массива только с одним значением вернёт вам строку. Например:

$object->tags = array('tag');
$tags = $object->tags;
// $tags will be the string "tag", NOT array('tag')

Чтобы всегда получать массив, просто приведите к массиву;

$tags = (array)$object->tags;

Чтение метаданных как объектов

elgg_get_metadata — лучшая функция для получения метаданных как объектов ElggMetadata:

Например, для получения даты рождения пользователя

elgg_get_metadata([
    'metadata_name' => 'dob',
    'guid' => $user_guid,
]);

Или для получения всех объектов метаданных:

elgg_get_metadata([
    'guid' => $user_guid,
    'limit' => false,
]);

Примечание

При получении метаданных по имени имена сопоставляются без учёта регистра. Поддерживайте чистоту кода и не смешивайте имена метаданных в верхнем и нижнем регистре.

Распространённые ошибки

«Добавление» метаданных

Обратите внимание, что вы не можете «добавлять» значения к массивам метаданных так, как если бы это были обычные массивы PHP. Например, следующее не сделает то, что, как кажется, должно сделать.

$object->tags[] = "tag four";

Попытка сохранить хеш-карты

Elgg не поддерживает хранение упорядоченных карт (пар имя/значение) в метаданных. Например, следующее не работает так, как вы могли бы сначала ожидать:

// Won't work!! Only the array values are stored
$object->tags = array('one' => 'a', 'two' => 'b', 'three' => 'c');

Вместо этого вы можете сохранить информацию следующим образом:

$object->one = 'a';
$object->two = 'b';
$object->three = 'c';

Хранение GUID в метаданных

Хотя есть некоторые случаи хранения GUID сущностей в метаданных, Relationships — гораздо лучшая конструкция для связи сущностей друг с другом.

Отношения

Отношения позволяют связывать сущности вместе. Примеры: у артиста есть фанаты, пользователь является членом организации и т.д.

Класс ElggRelationship моделирует направленное отношение между двумя сущностями, формируя утверждение:

«{subject} является {noun} для {target}

Имя API

Моделирует

Представляет

guid_one

Субъект

Какая сущность связывается

relationship

Существительное

Тип отношения

guid_two

Цель

Сущность, к которой привязан субъект

Тип отношения также может быть глаголом, формируя утверждение:

«{subject} {verb} {target}

Например, пользователь A «лайкает» пост блога B

Каждое отношение имеет направление. Представьте, что лучник стреляет стрелой в мишень; стрела движется в одном направлении, связывая субъект (лучника) с целью.

Отношение не подразумевает взаимности. То, что A подписан на B, не означает, что B подписан на A.

Отношения не имеют контроля доступа. Они никогда не скрываются из виду и могут редактироваться кодом на любом уровне привилегий, с оговоркой, что сущности в отношении могут быть невидимы из-за контроля доступа!

Работа с отношениями

Создание отношения

Например, чтобы установить, что «$user является фанатом $artist» (пользователь — субъект, артист — цель):

$success = $user->addRelationship($artist->guid, 'fan');

Это запускает событие [create, relationship], передавая созданный объект ElggRelationship. Если обработчик возвращает false, отношение не будет создано, и $success будет false.

Проверка отношения

Например, чтобы проверить, что «$user является фанатом $artist»:

if ($user->hasRelationship($artist->guid, 'fan')) {
    // relationship exists
}

Удаление отношения

Например, чтобы иметь возможность утверждать, что «$user больше не является фанатом $artist»:

$was_removed = $user->removeRelationship($artist->guid, 'fan');

Это запускает событие [delete, relationship], передавая связанный объект ElggRelationship. Если обработчик возвращает false, отношение останется, и $was_removed будет false.

Другие полезные функции:

  • \ElggRelationship->delete(): удалить по объекту

  • \ElggEntity->removeAllRelationships(): удалить те, которые относятся к сущности

Контроль доступа

Детализированный контроль доступа является одним из фундаментальных принципов дизайна в Elgg и функцией, которая находилась в центре системы на протяжении всей её разработки. Идея проста: пользователь должен иметь полный контроль над тем, кто видит созданный им элемент данных.

Контроль доступа в модели данных

Для достижения этого каждая сущность и аннотация содержит свойство access_id, которое, в свою очередь, соответствует одному из предопределённых контролей доступа или записи в таблице базы данных access_collections.

Предопределённые контроли доступа

  • ACCESS_PRIVATE (значение: 0) Приватный.

  • ACCESS_LOGGED_IN (значение: 1) Авторизованные пользователи.

  • ACCESS_PUBLIC (значение: 2) Публичные данные.

Пользовательские контроли доступа

Вы можете определить дополнительные группы доступа и назначить их сущности или аннотации. Определён ряд функций, которые помогут вам; см. Списки контроля доступа для получения дополнительной информации.

Как доступ влияет на получение данных

Все функции получения данных выше уровня базы данных — например, elgg_get_entities — будут возвращать только те элементы, к которым текущий пользователь имеет доступ для просмотра. Невозможно получить элементы, к которым текущий пользователь не имеет доступа. Это делает очень сложным создание уязвимости безопасности для получения данных.

Доступ на запись

Следующие правила регулируют доступ на запись:

  • Владелец сущности всегда может её редактировать

  • Владелец контейнера может редактировать всё, что в нём находится (обратите внимание, что это не означает, что владелец группы может редактировать всё, что в ней находится)

  • Администраторы могут редактировать всё

Вы можете переопределить это поведение с помощью события под названием permissions_check, которое передаёт рассматриваемую сущность любой функции, которая объявила, что хочет быть вызванной. Возврат true разрешит доступ на запись; возврат false запретит его. См. справку по событию permissions_check для получения более подробной информации.

Схема

База данных содержит ряд основных и вторичных таблиц. Вы можете отслеживать изменения схемы в engine/schema/migrations/

Набор символов базы данных должен быть utf8mb4, это обеспечит полную поддержку символов Unicode при хранении данных.

InnoDB

Начиная с Elgg 3.0 база данных использует движок InnoDB. Для правильной установки или миграции некоторые настройки могут потребовать корректировки в настройках базы данных.

  • innodb_large_prefix должен быть on

  • innodb_file_format должен быть Barracuda

  • innodb_file_per_table должен быть 1

Основные таблицы

Это описание основных таблиц. Имейте в виду, что в данной установке Elgg таблицы будут иметь префикс (обычно «elgg_»).

Таблица: entities

Это основная таблица Entities, содержащая пользователей, сайты, объекты и группы Elgg. При первой установке Elgg она автоматически заполняется вашим первым сайтом, первым пользователем и набором встроенных плагинов.

Она содержит следующие поля:

  • guid — автоинкрементный счётчик, создающий GUID, который однозначно идентифицирует эту сущность в системе

  • type — тип сущности: object, user, group или site

  • subtype — подтип сущности

  • owner_guid — GUID сущности владельца

  • container_guid — GUID, которым содержится эта сущность — либо пользователь, либо группа

  • access_id — контроли доступа к этой сущности

  • time_created — метка времени Unix, когда была создана сущность

  • time_updated — метка времени Unix, когда сущность была обновлена

  • last_action — метка времени Unix, когда пользователь последний раз выполнил действие или когда внутри сущности как контейнера что-то произошло

  • enabled — если это „yes“, сущность доступна, если „no“, сущность была отключена (Elgg обрабатывает её так, как если бы она была удалена, не удаляя её фактически из базы данных)

  • deleted — если это „yes“, сущность помечена как удалённая, если „no“ (по умолчанию), сущность видна на обычном сайте. Удалённые сущности можно просмотреть в корзине

  • time_deleted — метка времени Unix, когда сущность была удалена

Таблица: metadata

Эта таблица содержит Metadata, дополнительную информацию, прикреплённую к сущности.

  • id — уникальный идентификатор

  • entity_guid — сущность, к которой это прикреплено

  • name — строка имени

  • value — строка значения

  • value_type — класс значения: текст, bool или целое число

  • time_created — метка времени Unix, когда были созданы метаданные

Таблица: annotations

Эта таблица содержит Annotations, это отличается от Metadata.

  • id — уникальный идентификатор

  • entity_guid — сущность, к которой это прикреплено

  • name — строка имени

  • value — строка значения

  • value_type — класс значения: текст, bool или целое число

  • owner_guid — GUID владельца, который установил эту аннотацию

  • access_id — Контроли доступа к этой аннотации

  • time_created — метка времени Unix, когда была создана аннотация.

Таблица: relationships

Эта таблица определяет Relationships, они связывают одну сущность с другой.

  • guid_one — GUID сущности-субъекта.

  • relationship — тип отношения.

  • guid_two — GUID сущности-цели.

  • time_created — метка времени Unix, когда было создано отношение.

Вторичные таблицы

Таблица: access_collections

Эта таблица определяет коллекции доступа, которые предоставляют пользователям доступ к Entities или Annotations.

  • id — уникальный идентификатор

  • name — название коллекции доступа

  • owner_guid — GUID владеющей сущности (например, пользователь или группа)

  • subtype — подтип коллекции доступа (например, friends или group_acl)