Base de datos

Una discusión a conciencia del diseño y la motivación del modelo de datos de Elgg.

Resumen

In Elgg, everything runs on a unified data model based on atomic units of data called entities.

Plugins are discouraged from interacting directly with the database, which creates a more stable system and a better user experience because content created by different plugins can be mixed together in consistent ways. With this approach, plugins are faster to develop, and are at the same time much more powerful.

Every entity in the system inherits the ElggEntity class. This class controls access permissions, ownership, containment and provides consistent API for accessing and updating entity properties.

Existen dos maneras de extender las entidades con información adicional:

Metadata: This information describes the entity, it is usually
added by the author of the entity when the entity is created or updated. Examples of metadata include tags, ISBN number or a third-party ID, location, geocoordinates etc. Think of metadata as a simple key-value storage.
Annotations: This information extends the entity with properties usually
added by a third party. Such properties include ratings, likes, and votes.

The main differences between metadata and annotations:

  • metadata does not have owners, while annotations do
  • metadata is not access controlled, while annotations are
  • metadata is preloaded when entity is constructed, while annotations are only loaded on demand

These differences might have implications for performance and your business logic, so consider carefully, how you would like to attach data to your entities.

In certain cases, it may be benefitial to avoid using metadata and annotations and create new entities instead and attaching them via container_guid or a relationship.

Datamodel

The Elgg data model diagram

The Elgg data model diagram

Entidades

ElggEntity is the base class for the Elgg data model and supports a common set of properties and methods.

  • Un identificador numérico único (véase Identificadores únicos).
  • Permisos de acceso. Cuando un complemento solicita datos, no puede conseguir acceso a datos para los que el usuario actual no tiene permisos.
  • An arbitrary subtype (more below).
  • Un propietario.
  • El sitio al que pertenece la entidad.
  • A container, used to associate content with a group or a user.

Types

Actual entities will be instances of four different subclasses, each having a distinct type property and their own additional properties and methods.

Type PHP class Representa
object ElggObject Most user-created content, like blog posts, uploads, and bookmarks.
group ElggGroup An organized group of users with its own profile page
user ElggUser A user of the system
site ElggSite The site served by the Elgg installation

Each type has its own extended API. E.g. users can be friends with other users, group can have members, while objects can be liked and commented on.

Subtypes

Each entity must define a subtype, which plugins use to further specialize the entity. Elgg makes it easy to query specific for entities of a given subtype(s), as well as assign them special behaviors and views.

Subtypes are most commonly given to instances of ElggEntity to denote the kind of content created. E.g. the blog plugin creates objects with subtype "blog".

By default, users, groups and sites have the the subtypes of user, group and site respectively.

Plugins can use custom entity classes that extend the base type class. To do so, they need to register their class at runtime (e.g. in the 'init','system' handler), using elgg_set_entity_class(). For example, the blog plugin could use elgg_set_entity_class('object', 'blog', \ElggBlog::class).

Plugins can use elgg-plugin.php to define entity class via shortcut entities parameter.

Subtype Gotchas

  • Before an entity’s save() method is called, the subtype must be set by writing a string to the subtype property.
  • Subtype cannot be changed after saving.

Identificadores únicos

Un identificador único (GUID) es un número entero que identifica de manera inequívoca cada entidad de una instalación de Elgg. Se asigna de manera automática cuando la entidad se guarda por primera vez, y no se puede volver a cambiar nunca.

Algunas funciones de la API de Elgg funcionan con identificadores en vez de con instancias de ElggEntity.

ElggObject

El tipo de entidad ElggObject representa contenido arbitrario dentro de la instalación de Elgg. Puede representar, por ejemplo, artículos de blog, ficheros, etc.

Además de las propiedades estándar de ElggEntity, ElggObject ofrece las siguientes propiedades:

  • title: El título el objeto en texto escapado para HTML.
  • description Una descripción del objeto en HTML.

La mayor parte del resto de los datos sobre el objeto se almacenan mediante metadatos.

ElggUser

El tipo de entidad ElggUser representa a un usuario de la instalación de Elgg. Estos usuarios se marcan como desactivados hasta que se activan sus cuentas, a menos que sean creados desde el panel de administración.

Además de las propiedades estándar de ElggEntity, ElggUser ofrece las siguientes propiedades:

  • name: El nombre del usuario en texto plano. Por ejemplo: «Antonio García Fernández».
  • username: Nombre con el que el usuario accede al sistema. Por ejemplo, «antoniogf».
  • password: Una suma (hash) de la contraseña.
  • email: La dirección de correo electrónico.
  • language: El código del idioma predeterminado.
  • code: El código de la sesión, que a partir de la versión 1.9 está en una tabla separada.
  • last_action: La fecha en formato UNIX de la última vez que el usuario cargó una página.
  • prev_last_action: El valor anterior de last_action.
  • last_login: Fecha en formato UNIX de la última vez que el usuario accedió al sistema.
  • prev_last_login: El valor anterior de last_login.

ElggSite

The ElggSite entity type represents your Elgg installation (via your site URL).

Además de las propiedades estándar de ElggEntity, ElggSite ofrece las siguientes propiedades:

  • name: El nombre del sitio.
  • description: Una descripción del sitio.
  • url: La dirección URL del sitio.

ElggGroup

El tipo de entidad ElggGroup representa una asociación de usuarios de Elgg. Los usuarios pueden unirse a grupos, marcharse de ellos, o publicar contenido en ellos.

Además de las propiedades estándar de ElggEntity, ElggGroup ofrece las siguientes propiedades:

  • name: El nombre del grupo en texto escapado para HTML.
  • description Una descripción del grupo en HTML.

ElggGroup cuenta con métodos adicionales para gestionar su contenido y usuarios.

El complemento de grupos

Elgg incluye un complemento llamado «Grupos» que no debe confundirse con el tipo de entidad ElggGroup. El complemento provee la interfaz predeterminada con la que los usuarios del sitio pueden interactuar con los grupos. Cada grupo recibe un foro de discusiones y una página de perfil que pone a los usuarios en contacto con el contenido del grupo.

Usted puede cambiar la experiencia del usuario mediante los medios tradicionales de extender los complementos, o substituyendo el complemento de «Grupos» por su propio complemento.

Desarrollar un complemento compatible con grupos

Los desarrolladores de complementos no tienen por qué preocuparse mucho por que sus complementos sean compatibles con los grupos o sus funcionalidades, pero aquí van algunas claves a tener en cuenta:

Añadir contenido

Incluyendo el identificador del grupo como container_guid mediante un campo de entrada oculto, puede usar un mismo formulario para añadir contenido tanto a un usuario como a un grupo.

Use ElggEntity->canWriteToContainer() to determine whether or not the current user has the right to add content to a group.

Tenga en cuenta que entonces tendrá que pasarle el identificador del contenedor o el nombre del usuario a la página responsable de publicar el contenido y el valor que lo acompañe, de forma que se pueda almacenar en su formulario como un campo de entrada oculto, para pasar fácilmente sus acciones. Dentro de una acción de crear, necesitará obtener este campo de entrada y guardarlo como una propiedad del nuevo elemento cuyo valor predeterminado es el contenedor del usuario actual:

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

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

    if (!$container->canWriteToContainer($user->guid)) {
        // register error and forward
    }
} else {
    $container_guid = elgg_get_logged_in_user_guid();
}

$object = new ElggObject;
$object->container_guid = $container_guid;

...

$container = get_entity($container_guid);
forward($container->getURL());

Alternando entre usuarios y grupos

Lo cierto es que ElggGroup simula la mayor parte de los métodos de ElggUser. Puede obtener el icono del grupo, el nombre u otros datos con los mismos métodos, y si pide los contactos de un grupo, obtendrá sus miembros. Esto ha sido diseñado de manera específica para que usted pueda alternar entre usuarios y grupos de manera sencilla desde código.

Propiedad

Las entidades tienen una propiedad, owner_guid, cuyo valor es el identificador único del propietario de la entidad. Habitualmente el identificador corresponde a un usuario, aunque tanto el propio sitio como sus usuarios no suelen tener propietario (el valor de la propiedad es 0).

La propiedad de una entidad determina, en parte, si un usuario puede o no acceder a la entidad o modificarla.

Contenedores

Para poder buscar contenido fácilmente por usuario o por grupo, el contenido se define generalmente como «contenido» por el usuario que lo publicó o el grupo en el que se publicó. Eso significa que a la propiedad container_guid de los nuevos objetos se le dará como valor el identificador único de la instancia de ElggUser actual o de la instancia de ElggGroup de destino.

Por ejemplo, tres artículos de blog podrían pertenecer a autores distintos, pero estar todos contenidos por el grupo en el que se publicaron.

Nota: Esto no es siempre cierto. Las entidades de comentarios son propiedad del que comentan, y en algunos complementos de terceros el contenedor podría usarse para modelar una relación de padre-hijo entre entidades (por ejemplo, un objeto «carpeta» que contiene un objeto «fichero»).

Anotaciones

Annotations are pieces of data attached to an entity that allow users to leave ratings, or other relevant feedback. A poll plugin might register votes as annotations.

Las anotaciones se almacenan como instancias de la clase ElggAnnotation.

Cada anotación cuenta con:

  • Un tipo de anotación interno (como comment, «comentario»).
  • Un valor (que puede ser una cadena de texto o un número entero).
  • Un permiso de acceso distinto del de la entidad a la que está anexado.
  • Un propietario.

Like metadata, values are stored as strings unless the value given is a PHP integer (is_int($value) is true), or unless the $vartype is manually specified as integer.

Añadir una anotación

La forma más sencilla de añadir una anotación a una entidad es usar el método annotate de dicha entidad. La declaración de dicho método es la siguiente:

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' or 'integer'
)

Por ejemplo, para dejar una puntuación en una entidad, puede llamar a:

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

Leer anotaciones

Para obtener las anotaciones de un objeto, puedes llamar al método siguiente:

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

Por si el tipo de tu anotación lidia en gran medida con números enteros, tiene a su disposición una serie de funciones matemáticas que pueden resultarle útiles:

$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

Funciones de asistencia útiles

Comentarios

Si quiere ofrecer la funcionalidad de dejar comentarios en los objetos de su complemento, la siguiente función se lo facilitará, formulario y acciones:

function elgg_view_comments(ElggEntity $entity)

Metadatos

En Elgg, los metadatos le permiten almacenar alguna información adicional en una entidad más allá de los campos predeterminados que ofrece la entidad. Por ejemplo, las instancias de ElggObject sólo ofrecen los campos de entidad básicos y campos para un título y una descripción, pero puede que quieras incluir etiquetas o un número ISBN. De manera similar, puede que quieras que los usuarios tengan la posibilidad de guardar una fecha de cumpleaños.

Internamente, los metadatos se almacenan como una instancia de ElggMetadata, pero usted no tiene que preocuparse de eso en la práctica (pero si le interesa, échele una ojeada la la documentación de referencia de la clase). Lo único que necesita saber es que:

  • Metadata has an owner, which may be different to the owner of the entity it’s attached to
  • Usted podría tener varios elementos de cata tipo de metadatos anexados a una única entidad.
  • Like annotations, values are stored as strings unless the value given is a PHP integer (is_int($value) is true), or unless the $value_type is manually specified as integer (see below).

Nota

As of Elgg 3.0, metadata no longer have access_id.

El caso más simple

Añadir metadatos

Para añadir un metadato a una entidad, llama a:

$entity->metadata_name = $metadata_value;

Por ejemplo, para añadir una fecha de cumpleaños a un usuario:

$user->dob = $dob_timestamp;

O para añadir un par de etiquetas a un objeto:

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

Al añadir metadatos mediante el procedimiento anterior:

  • El usuario actual se convierte en el propietario de los metadatos.
  • Al reasignar un metadato, este substituye cualquier valor previo de ese mismo metadato.

Esto funciona bien para la mayoría de los casos. Tenga cuidado de anotar qué atributos son metadatos y cuales son parte integral del tipo de entidad con el que está trabajando. No tiene por qué guardar una entidad después de añadirle o actualizar sus metadatos. Pero si lo que ha cambiado son sus atributos integrales, debe guardar la entidad para preservar sus cambios. Por ejemplo, si ha cambiado el identificador de acceso de un objeto de tipo ElggObject, tiene que guardar el objeto, o de lo contrario el cambio no se hará efectivo en la base de datos.

Nota

As of Elgg 3.0, metadata’s access_id property is ignored.

Leer metadatos

Para obtener un metadato, haga lo mismo que haría con una propiedad de la entidad:

$tags_value = $object->tags;

Nota: El método anterior devolverá el valor absoluto del metadato. Para obtener el metadato como una instancia de ElggMetadata, use los métodos descritos en la sección Un mayor control más adelante.

Si almacena varios valores en este metadato (como en el ejemplo de las etiquetas), obtendrá un vector (array) con todos esos valores. Si sólo almacenó un valor, obtendrá una cadena de texto o un número entero. Si almacenó un vector con un único valor, el metadato devolverá una cadena de texto. Por ejemplo:

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

Para obtener siempre un vector, no tiene más que invocar el método como tal:

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

Reading metadata as objects

elgg_get_metadata es la mejor función para obtener metadatos como instancias de ElggMetadata:

Por ejemplo, para obtener la fecha de cumpleaños (DOB) de un usuario:

elgg_get_metadata(array(
    'metadata_name' => 'dob',
    'metadata_owner_guid' => $user_guid,
));

O para obtener todos los objetos de metadatos:

elgg_get_metadata(array(
    'metadata_owner_guid' => $user_guid,
    'limit' => 0,
));

Errores habituales

“Anexar” metadatos

No puede “anexar” valores a vectores de metadatos como si fuesen simples vectores de PHP. Por ejemplo, el siguiente código no hace lo que parece que debería hacer:

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

Intentar almacenar mapas de sumas («hashmaps»)

Elgg no permite almacenar mapas ordenados (parejas con nombre y valor) en metadatos. Por ejemplo, el siguiente código no funcionaría como uno cabría esperar en un principio:

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

En vez de eso, puede hacer lo siguiente para almacenar la información:

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

Almacenar identificadores únicos en metadatos

Aunque existen algunos casos en los que tiene sentido almacenar identificadores únicos en metadatos, las relaciones son una construcción mucho mejor para relacionar unas entidades con otras.

Relaciones

Las relaciones permiten asociar entidades las unas con las otras. Por ejemplo: un artista que tiene fans, un usuario que es miembro de una organización, etc.

La clase ElggRelationship modela una relación con dirección entre dos entidades, dando lugar a la expresión:

«{sujeto} es un {substantivo} de {objeto}
Nombre en el API Modela Representa
guid_one Sujeto La entidad que se está relacionando.
relationship Substantivo El tipo de la relación.
guid_two Objeto La entidad con la que se relaciona el sujeto.

Alternativamente, el tipo de relación puede ser un verbo, dando lugar a la expresión:

«{sujeto} {verbo} {objeto}

Por ejemplo, el usuario A «está de acuerdo con» el comentario B.

Toda relación tiene una dirección. Imagínese un arquero disparando una flecha al objeto. La flecha se mueve en una dirección, relacionando el sujeto (el arquero) con el objeto.

Las relaciones no implicar reciprocidad. Que A siga a B no significa que B siga a A.

Las relaciones_ carecen de control de acceso. Nunca se les ocultan a las vistas, y pueden editarse mediante código independientemente del nivel de privilegios, con la pega de que las entidades de la relación podrían sí ser invisibles por culpa del control de acceso.

Trabajar con relaciones

Crear una relación

Por ejemplo, para establecer que «$user es un fan de $artist», donde $user (usuario) es el sujeto y $artist (artista) el objeto:

// option 1
$success = add_entity_relationship($user->guid, 'fan', $artist->guid);

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

Esto desencadena el evento [create, relationship], pasando a éste el objeto de tipo ElggRelationship creado. Si un manejador devuelve false, la relación no se creará y $success (que indica si todo fue correctamente) pasará a ser false.

Verificar una relación

Por ejemplo, para verificar que «$user es fan de $artist»:

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

Tenga en cuenta que, si la relación existe, check_entity_relationship() devuelve una instancia de ElggRelationship:

$relationship = check_entity_relationship($user->guid, 'fan', $artist->guid);
if ($relationship) {
    // use $relationship->id or $relationship->time_created
}

Eliminar una relación

Por ejemplo, para poder asegurarse de que «$user ya no es fan de $artist»:

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

Esto desencadena el evento [delete, relationship], pasando a éste el objeto de tipo ElggRelationship asociado. Si un manejador devuelve false, la relación seguirá existiendo y $was_removed («se eliminó») pasará a ser false.

Otras funciones útiles:

  • delete_relationship() : eliminar la relación con el identificador indicado.
  • remove_entity_relationships() : delete those relating to an entity

Control de acceso

Los controles de acceso granular son uno de los principio de diseño fundamentales de Elgg, y una funcionalidad que ha estado en el centro de el sistema a lo largo de su desarrollo. La idea es sencilla: un usuario debería tener todo el control sobre quién puede ver un dato que ha creado dicho usuario.

Controles de acceso en el modelo de datos

In order to achieve this, every entity and annotation contains an access_id property, which in turn corresponds to one of the pre-defined access controls or an entry in the access_collections database table.

Controles de acceso predefinidos

  • ACCESS_PRIVATE (value: 0) Private.
  • ACCESS_LOGGED_IN (value: 1) Logged in users.
  • ACCESS_PUBLIC (value: 2) Public data.

Controles de acceso definidos por los usuarios

You may define additional access groups and assign them to an entity, or annotation. A number of functions have been defined to assist you; see the Access Control Lists for more information.

Cómo el acceso afecta a la obtención de datos

All data retrieval functions above the database layer - for example elgg_get_entities will only return items that the current user has access to see. It is not possible to retrieve items that the current user does not have access to. This makes it very hard to create a security hole for retrieval.

Acceso de escritura

Las siguientes reglar rigen el acceso de escritura:

  • El propietario de una entidad siempre puede editarla.
  • El propietario de un contenedor puede editar todo lo que hay en el contenedor. Nota: Ello no significa que el propietario de un grupo pueda editar cualquier cosa dentro del grupo).
  • Los administradores pueden editarlo todo.

You can override this behaviour using a plugin hook called permissions_check, which passes the entity in question to any function that has announced it wants to be referenced. Returning true will allow write access; returning false will deny it. See the plugin hook reference for permissions_check for more details.

Esquema

The database contains a number of primary and secondary tables. You can follow schema changes in engine/schema/migrations/

Tablas principales

Esta es una descripción de las tablas principales. No se olvide de que en toda instalación de Elgg estas tablas tienen un prefijo, que suele ser «elgg_».

Tabla: entidades

Esta es la tabla principal de entidades, y contiene a los usuarios de Elgg, los sitios, los objetos y los grupos. Cuando instala Elgg por primera vez, la tabla se rellena automáticamente con su primer sitio.

Contiene los siguientes campos:

  • guid An auto-incrementing counter producing a GUID that uniquely identifies this entity in the system
  • type: El tipo de la entidad (object, user, group o site).
  • subtype A subtype of entity
  • owner_guid The GUID of the owner’s entity
  • container_guid The GUID this entity is contained by - either a user or a group
  • access_id Access controls on this entity
  • time_created Unix timestamp of when the entity is created
  • time_updated Unix timestamp of when the entity was updated
  • enabled If this is “yes” an entity is accessible, if “no” the entity has been disabled (Elgg treats it as if it were deleted without actually removing it from the database)

Tabla: metadata

Esta tabla contiene metadatos, información adicional anexada a una entidad.

  • id A unique IDentifier
  • entity_guid The entity this is attached to
  • name The name string
  • value The value string
  • value_type The value class, either text or an integer
  • time_created Unix timestamp of when the metadata is created
  • enabled If this is “yes” an item is accessible, if “no” the item has been disabled

Tabla: annotations

Esta tabla contiene anotaciones, que no son lo mismo que metadatos.

  • id A unique IDentifier
  • entity_guid The entity this is attached to
  • name The name string
  • value The value string
  • value_type The value class, either text or an integer
  • owner_guid The owner GUID of the owner who set this annotation
  • access_id An Access controls on this annotation
  • time_created Unix timestamp of when the annotation is created.
  • enabled If this is “yes” an item is accessible, if “no” the item has been disabled

Tabla: relationships

Esta tabla define relaciones, las cuales enlazan unas entidades con otras.

  • guid_one: El identificador de la entidad sujeto.
  • relationship: El tipo de la relación.
  • guid_two: El identificador de la entidad objeto.

Secundairy tables

Table: access_collections

This table defines Access Collections, which grant users access to Entities or Annotations.

  • id A unique IDentifier
  • *name The name of the access collection
  • owner_guid The GUID of the owning entity (eg. a user or a group)
  • subtype the subtype of the access collection (eg. friends or group_acl)