Pular para conteúdo

Segmentos

Segmentos permitem segmentar usuarios via criterios composiveis. Um segmento e uma regra declarativa (ex: "usuarios com perfil X e na coorte Y") que o framework avalia como SQL subquery — sem materializar listas.

O sistema faz parte do Core Capability CC-07 (core.segments) e esta sempre disponivel via core extension.


Exemplo rapido

Avaliar um segmento existente e obter a subquery de membros:

use local_middag\facade\segments;

// Obter subquery SQL dos membros do segmento
$subquery = segments::get_subquery(segment_id: 15);

// Usar a subquery em uma query composta
global $DB;
$users = $DB->get_records_sql(
    "SELECT u.* FROM {user} u WHERE u.id IN ({$subquery->sql})",
    $subquery->params
);

// Ou apenas contar membros (respeitando cache)
$count = segments::evaluate(segment_id: 15);

Conceitos

Conceito Descricao
Segment Regra declarativa que define um grupo de usuarios
Criteria Condicao individual dentro do segmento (ex: campo de perfil, coorte)
Criteria type Categoria da condicao (ex: profile_field, cohort_membership)
Criteria provider Implementacao que sabe gerar SQL e validar config para um criteria type
segment_subquery Value object retornado pela avaliacao: contem SQL, parametros e contagem

Segmentos nao retornam listas de IDs. Retornam subqueries SQL para composicao e performance em operacoes em massa.


Referencia da API

segments_interface (contract @api)

Metodo Descricao
get_subquery(int $segment_id) Retorna segment_subquery com SQL, parametros e contagem estimada
evaluate(int $segment_id) Avalia o segmento e retorna contagem de membros (respeita cache)
get_criteria_types() Lista tipos de criterio disponiveis (built-in + extensions)
get_available_fields() Lista campos filtraveis de todos os providers registrados

Acesso via facade:

use local_middag\facade\segments;

$subquery = segments::get_subquery($id);
$count    = segments::evaluate($id);
$types    = segments::get_criteria_types();
$fields   = segments::get_available_fields();

segment_subquery (value object @api)

final readonly class segment_subquery
{
    public string $sql;       // SQL subquery retornando user IDs
    public array  $params;    // Parametros bound para a subquery
    public int    $count;     // Contagem estimada (pode ser cached)
}

Uso tipico:

$subquery = segments::get_subquery(15);

// Compor com outra query
$sql = "SELECT u.id, u.email
        FROM {user} u
        WHERE u.id IN ({$subquery->sql})
        AND u.suspended = 0";

$users = $DB->get_records_sql($sql, $subquery->params);

Implementar um criteria provider

Extensions podem adicionar novos tipos de criterio implementando criteria_provider_interface e registrando o provider durante boot().

Exemplo: criterio por lingua do usuario

namespace local_meuplugin\extensions\idiomas;

use local_middag\framework\contract\criteria_provider_interface;
use local_middag\framework\contract\segment_subquery;

class language_criteria_provider implements criteria_provider_interface
{
    /**
     * Identificador unico deste tipo de criterio.
     */
    public function get_type(): string
    {
        return 'user_language';
    }

    /**
     * Campos filtraveis expostos por este provider.
     */
    public function get_fields(): array
    {
        return [
            [
                'name'       => 'lang',
                'label'      => get_string('language'),
                'category'   => 'profile',
                'operators'  => ['equals', 'not_equals', 'in'],
                'is_advanced' => false,
            ],
        ];
    }

    /**
     * Gera subquery SQL para a configuracao de criterio.
     */
    public function build_sql(array $config): segment_subquery
    {
        $lang = $config['value'] ?? 'pt_br';

        return new segment_subquery(
            sql: "SELECT id AS userid FROM {user} WHERE lang = ?",
            params: [$lang],
        );
    }

    /**
     * Descricao legivel do criterio para exibicao.
     */
    public function describe(array $config): string
    {
        $lang = $config['value'] ?? '?';
        return get_string('language') . ' = ' . $lang;
    }

    /**
     * Valida a configuracao antes de persistir.
     */
    public function validate_config(array $config): bool
    {
        return !empty($config['value']) && is_string($config['value']);
    }
}

Registro no boot()

namespace local_meuplugin\extensions\idiomas;

class idiomas_extension extends \local_middag\base\extension
{
    public function boot(): void
    {
        // Registrar o provider no criteria registry
        $registry = $this->container->get(
            \local_middag\framework\contract\segments_interface::class
        );

        // O registro concreto depende da implementacao do registry;
        // consulte a documentacao da versao para o metodo exato.
    }
}

Referencia do criteria_provider_interface

Metodo Descricao
get_type(): string Identificador unico do tipo de criterio
get_fields(): array Lista de campos filtraveis com label, categoria, operadores e visibilidade
build_sql(array $config) Gera segment_subquery para a configuracao dada
describe(array $config) Descricao legivel para display e audit
validate_config(array $config) Valida a configuracao antes de persistir (true = valida)

Estrutura do campo retornado por get_fields()

[
    'name'        => 'lang',           // Identificador do campo
    'label'       => 'Idioma',         // Label para exibicao
    'category'    => 'profile',        // Categoria para agrupamento na UI
    'operators'   => ['equals', 'in'], // Operadores suportados
    'is_advanced' => false,            // Se e campo avancado (oculto por padrao)
]

Patterns

Combinar criterios em um provider

Um provider pode expor multiplos campos filtraveis. O segment builder combina criterios de multiplos providers via AND/OR:

public function get_fields(): array
{
    return [
        ['name' => 'lang',    'label' => 'Idioma',  'category' => 'profile', 'operators' => ['equals', 'in'], 'is_advanced' => false],
        ['name' => 'country', 'label' => 'Pais',    'category' => 'profile', 'operators' => ['equals', 'in'], 'is_advanced' => false],
        ['name' => 'city',    'label' => 'Cidade',   'category' => 'profile', 'operators' => ['equals', 'contains'], 'is_advanced' => true],
    ];
}

Usar segmentos em conteudo condicional

Combine segmentos com shortcodes ou hooks para entregar conteudo direcionado:

use local_middag\facade\segments;

// Em um hook ou controller
$subquery = segments::get_subquery($segment_id);

global $DB, $USER;
$is_member = $DB->record_exists_sql(
    "SELECT 1 FROM ({$subquery->sql}) sub WHERE sub.userid = ?",
    array_merge($subquery->params, [$USER->id])
);

if ($is_member) {
    // Exibir conteudo exclusivo para este segmento
}

Contar membros para relatorio

use local_middag\facade\segments;

$segments_list = [10, 15, 22];
$report = [];

foreach ($segments_list as $id) {
    $report[$id] = segments::evaluate($id);
}
// $report = [10 => 342, 15 => 1205, 22 => 87]

Anti-patterns

Materializar a lista de IDs

Segmentos retornam subqueries por design. Nao converta para array de IDs em operacoes em massa:

// ERRADO: materializa milhares de IDs na memoria
$subquery = segments::get_subquery(15);
$ids = $DB->get_fieldset_sql($subquery->sql, $subquery->params);
// Depois usa IN(...) com milhares de valores

// CORRETO: compor a subquery diretamente
$sql = "SELECT u.* FROM {user} u WHERE u.id IN ({$subquery->sql})";
$users = $DB->get_records_sql($sql, $subquery->params);

Avaliar segmentos em loop por usuario

O segmento avalia todos os membros de uma vez. Nao chame evaluate() ou get_subquery() dentro de um loop por usuario:

// ERRADO: N+1 queries
foreach ($users as $user) {
    $count = segments::evaluate(15); // mesma query repetida
}

// CORRETO: avaliar uma vez, compor subquery
$subquery = segments::get_subquery(15);

Provider sem validacao

Sempre implemente validate_config() com verificacoes reais. Um provider que sempre retorna true permite configuracoes invalidas:

// ERRADO
public function validate_config(array $config): bool
{
    return true; // aceita qualquer coisa
}

// CORRETO
public function validate_config(array $config): bool
{
    return !empty($config['value']) && is_string($config['value']);
}

Diferenca entre Segments e Conditions

Aspecto Segments (CC-07) Conditions (CC-09)
Pergunta "Quais usuarios pertencem a este grupo?" "Em quais contextos esta regra se aplica?"
Alvo Usuarios (user IDs) Contextos Moodle (categorias, cursos, atividades)
Retorno segment_subquery (SQL de user IDs) bool (match) ou list<int> (context IDs)
Uso Audiencia, targeting, conteudo segmentado Visibilidade, filtro por area do site

Veja o guia de Conditions para regras baseadas em contexto.


Referencias

  • ADR-607 -- Modelo de Subdominios Core (CC-07)
  • Conditions -- regras de inclusao/exclusao por contexto
  • Hooks -- integrar segmentos com o sistema reativo