Looking to hire Laravel developers? Try LaraJobs

analytics-laravel maintained by rflex

Description
Server-side product analytics (PostHog) for rFlex Laravel systems: events, identify/group, feature flags.
Author
Last update
2026/10/26 16:33 (dev-main)
License
Links
Downloads
2

Comments
comments powered by Disqus

rflex/analytics-laravel

Analytics de producto del lado servidor (PostHog) para los systems Laravel de rFlex: eventos, identify/group y feature flags. Compatible con PHP 7.3 a 8.x y Laravel 7 a 9. No depende de posthog/posthog-php (exige PHP 8 desde la v3); habla con la API HTTP de PostHog usando el cliente HTTP de Laravel, por lo que en tests se fakea con Http::fake().

Para el navegador (GdP2 y webapp) existe el paquete hermano @rflex-spa/analytics (packages/analytics-js). Ambos comparten el catálogo de eventos y el esquema de identidad.

Instalación

Hoy el paquete vive en starbase/packages/analytics-laravel. Cuando se publique en Packagist:

composer require rflex/analytics-laravel
php artisan vendor:publish --tag=analytics-config   # opcional

El service provider y el alias Analytics se registran por auto-discovery.

Variables de entorno

Variable Default Uso
ANALYTICS_ENABLED false Interruptor general. Apagado: no se bufferiza, no hay HTTP ni jobs, los flags devuelven el default
ANALYTICS_DRIVER posthog posthog, log (escribe los payloads al log, sin red) o null
POSTHOG_API_KEY Token de proyecto (phc_...). Nunca una personal API key
POSTHOG_HOST https://us.i.posthog.com https://eu.i.posthog.com para proyectos en la UE
ANALYTICS_DELIVERY inline inline envía al terminar el request; queue despacha SendAnalyticsBatch
ANALYTICS_QUEUE_CONNECTION / ANALYTICS_QUEUE null Conexión y cola del job (null = las por defecto; sync permitido)
ANALYTICS_FLAGS_ENABLED false Habilita la consulta de feature flags (está en el request path)
ANALYTICS_FLAGS_TIMEOUT / ANALYTICS_FLAGS_CACHE_TTL 0.3 / 60 Presupuesto en segundos y TTL del cache de flags
ANALYTICS_SYSTEM / ANALYTICS_ENVIRONMENT / ANALYTICS_APP_VERSION Propiedades estándar que viajan en todo evento
ANALYTICS_LOG_CHANNEL null Canal de log para el driver log y para los avisos de entrega

El resto (batch.size, batch.max_buffer, sampling, catalog.strict, posthog.retries) se ajusta en config/analytics.php.

Uso

use Rflex\Analytics\Catalog\Events;
use Rflex\Analytics\Catalog\Props;
use Rflex\Analytics\Facades\Analytics;
use Rflex\Analytics\Identity\IdentityFactory;

$identity = IdentityFactory::fromInstanceUser(clinica(), 'admin', $admin->idadmin, [
    'organization' => clinica(),
]);

Analytics::identify($identity, ['email' => $admin->correo, 'role' => 'admin']);
Analytics::group('organization', clinica(), ['name' => config('rflex.datosInstancia.nombre')]);
Analytics::track(Events::REPORT_EXPORTED, [Props::REPORT => 'honorarios', Props::FORMAT => 'xlsx'], $identity);

if (Analytics::isFeatureEnabled('nuevo-reporte', $identity)) {
    // ...
}

Cada system bindea un IdentityResolver para que track() sin identidad explícita use al usuario autenticado, y opcionalmente un EnablementResolver para combinar el env con una configuración por tenant:

$this->app->bind(IdentityResolver::class, InstanceIdentityResolver::class);
$this->app->bind(EnablementResolver::class, function () {
    return new class implements EnablementResolver {
        public function enabled(): bool
        {
            return (bool) config('analytics.enabled') && (bool) getConfig('general.habilitarAnalytics');
        }
    };
});

Analytics::setContext(['client_code' => clinica()]) agrega propiedades a todos los eventos del proceso.

Esquema de identidad (compartido con el paquete JS)

Actor distinct_id Ejemplo
Usuario nominus nominus:{nominus_user_id} nominus:42
Admin de instancia (api, webapp, app sin nominus) {client_code}:admin:{idadmin} clinicax:admin:17
Trabajador de instancia {client_code}:worker:{idusuario} clinicax:worker:9031
Usuario presenza sin id nominus presenza:{user_id} presenza:7
Anónimo uuid v4 con $process_person_profile: false

Reglas: client_code en minúsculas y sin espacios; el rol del id es solo admin o worker (DT e interno rflex van como person props); sin hash. Grupos: organization = código de organización (clinica() en api, organizations.code en nominus y presenza), holding = uuid del holding. Person props permitidas: email, nombre, rol, portal, plataforma, client_code, client_name. Nunca RUT.

Agregar un evento

  1. Constante en Rflex\Analytics\Catalog\Events (snake_case, inglés) y la misma clave en el catálogo del paquete JS.
  2. Nombres de propiedades en Props si son nuevos.
  3. Si el volumen es alto, tasa en config('analytics.sampling') y Props::SAMPLE_KEY con el id de la entidad para que el muestreo sea determinista. El evento sale con sample_rate para reponderar.
  4. Con catalog.strict = true (recomendado en local y testing) los nombres fuera del catálogo lanzan InvalidArgumentException.

Entrega

Situación Qué pasa
enabled=false track/identify/group/flush retornan sin tocar el resolver ni el buffer
delivery=inline Se bufferiza y se envía al llegar a batch.size o en $app->terminating() (tras responder en php-fpm). En Vapor terminate() corre dentro de la invocación: usar queue
delivery=queue Mismo buffer; al vaciarse se despacha SendAnalyticsBatch (arrays planos, tries = 1) a la conexión/cola configurada. La cola analytics hay que agregarla en Horizon, supervisord o vapor.yml
Comandos artisan Flush al terminar el comando o al llenarse el buffer
Workers de cola Flush después de cada JobProcessed

El driver nunca lanza: los errores HTTP quedan en el log como warning. Si el buffer supera batch.max_buffer se descartan los mensajes más antiguos y se avisa una vez.

Feature flags

POST /flags?v=2 con fallback a /decide?v=3; cache por identidad y grupos durante cache_ttl segundos (sets incompletos no se cachean); cualquier error o timeout devuelve el default. flags.enabled es independiente de los eventos porque la consulta bloquea el request. Para evaluación local a futuro, bindear otra implementación de FlagEvaluator.

Demostrar que apagado se comporta igual

config(['analytics.enabled' => false]);
Http::fake();
Queue::fake();
// ... ejercer el flujo ...
Http::assertNothingSent();
Queue::assertNothingPushed();

Para ver los payloads sin red: ANALYTICS_ENABLED=true ANALYTICS_DRIVER=log.

En los tests de un system: $fake = Analytics::fake(); y luego assertTracked, assertNotTracked, assertNothingTracked, assertIdentified, assertGrouped, setFlag.

Tests del paquete

Desde la raíz de starbase:

packages/analytics-laravel/bin/test-php73.sh    # PHP 7.3.25 / Laravel 7 / Guzzle 6 (imagen api)
packages/analytics-laravel/bin/test-php82.sh    # PHP 8.2 / Laravel 9 (imagen presenza)
packages/analytics-laravel/bin/lint-compat.sh   # php -l + PHPCompatibility 7.3-

Cada script resuelve dependencias en un vendor propio (.vendor/php73, .vendor/php82) y no deja composer.lock. Laravel 7, 8 y 9 tienen avisos de seguridad en Packagist; composer.json desactiva el bloqueo por advisories solo para esta matriz de pruebas. La imagen api necesita systems/api/.env para construirse.

Puntos de extensión

  • DriverFactory::extend('nombre', fn) para otro destino.
  • EnablementResolver para flags por tenant.
  • FlagEvaluator para evaluación local.
  • AnalyticsFake para los tests de los systems.