Skip to content

Plus / Audience Management REST ​

Flow ID: AD-46 | Module(s): Plus (domain layer) | Complexity: Medium Last Updated: 2026-09-14

Business Context ​

The Plus module provides customer audience segmentation through a modern REST API. Audiences are named segments of customers defined by tag-based criteria. Each audience can contain multiple criteria rules that reference customer tags and tag groups, with a configurable matching logic (AND/OR).

Audiences are used by the marketing system for targeted campaigns, email lists, and personalized promotions. The criteria system allows building dynamic audience definitions like "customers tagged with 'VIP' AND 'Athens'" or "customers tagged with 'Newsletter' OR 'Active buyer'".

The audience-to-customer relationship is maintained via a many-to-many junction table (shop_customer_audience), populated by the AddCustomersToAudience scheduled job based on the audience's criteria.


API Reference ​

REST Endpoints -- Audience ​

MethodPathActionDescription
GET/rest/plus/audienceindexList audiences (paginated)
GET/rest/plus/audience/itemitemGet single audience by filter
GET/rest/plus/audience/{id}showGet audience by ID
POST/rest/plus/audiencestoreCreate an audience
POST/rest/plus/audience/{id}updateUpdate an audience
DELETE/rest/plus/audience/{id}destroyDelete an audience

Filters: id (exact), name (partial), criteriaType (exact) Sorts: id, name, creationDatetimeRelations: criteria (one_to_many), customers (many_to_many, backend-only in the resource output — see note below, Advisable-com/ecommercen#615)

REST Endpoints -- AudienceCriteria ​

MethodPathActionDescription
GET/rest/plus/audience-criteriaindexList criteria entries (paginated)
GET/rest/plus/audience-criteria/itemitemGet single criteria by filter
GET/rest/plus/audience-criteria/{id}showGet criteria by ID
POST/rest/plus/audience-criteriastoreCreate a criteria entry
POST/rest/plus/audience-criteria/{id}updateUpdate a criteria entry
DELETE/rest/plus/audience-criteria/{id}destroyDelete a criteria entry

Filters: id (exact), audienceId (exact), tagId (exact), groupId (exact) Sorts: id, audienceIdRelations: audience (belongs_to)

Legacy Admin Routes ​

No dedicated legacy admin routes for audience REST management. The legacy audience admin is in ecommercen/audience/controllers/Adv_audience_admin.php but manages audiences through a different interface.


Code Flow ​

Creating an Audience with Criteria ​

  1. POST /rest/plus/audience with:

    json
    {
      "name": "VIP Athens Customers",
      "criteriaType": 2,
      "creationDatetime": "2026-04-04T10:00:00"
    }
  2. Controllers\Audience::store() -> doStore() -> WriteService::create().

  3. WriteData::fromArray() maps input to name, criteria_type, creation_datetime, etc.

  4. Validator::validateForCreate() runs (currently stub).

  5. WriteRepository::insert() inserts into plus_audience.

  6. Returns the created audience via Resource.

  7. POST /rest/plus/audience-criteria for each criteria rule:

    json
    {
      "audienceId": 1,
      "tagId": 42,
      "groupId": 5,
      "customValue": null
    }
  8. Controllers\AudienceCriteria::store() inserts into plus_audience_criteria.

Querying an Audience with Nested Data ​

  1. GET /rest/plus/audience/1?with=criteria,customers
  2. Controllers\Audience::show(1) delegates to service.
  3. Service::get(1, ['criteria', 'customers']) fetches the audience and loads relations:
    • criteria: ONE_TO_MANY from plus_audience_criteria via audience_id
    • customers: MANY_TO_MANY from shop_customer via shop_customer_audience junction table
  4. Resource returns (backend/admin caller — /rest/plus/audience is itself auth => 'backend' + ADMIN/MARKETING, so this is the only context that reaches it directly):
    json
    {
      "id": 1,
      "name": "VIP Athens Customers",
      "criteriaType": 2,
      "creationDatetime": "2026-04-04T10:00:00",
      "updateDatetime": null,
      "userId": 1,
      "criteria": [...],
      "customers": [...]
    }

Why customers carries an explicit backend gate despite the endpoint already being backend-only (Advisable-com/ecommercen#615). This Resource is not reached only through its own endpoint — SlideResource embeds audience, and Slide/Slider/Group reads are auth => 'guest', so ?with=audience.customers / ?with=slides.audience.customers / ?with=sliders.slides.audience.customers all arrive at this same Resource in a storefront (guest or customer) context. AudienceResource::addRelationToData() therefore omits customers entirely unless $this->context?->isBackend(), and each embedded CustomerResource independently withholds its personal fields from anyone but backend or the customer themselves. For the backend caller shown above nothing changes — every field and relation in the example is still returned. See AD-21 Slider / Slideshow Management for the guest-readable chain this closes.


Domain Layer ​

Audience (src/Domains/Plus/Audience/) ​

ComponentClassDescription
EntityRepository\Entityid, name, criteria_type, creation_datetime, update_datetime, user_id
RepositoryRepository\RepositoryTable plus_audience
RepositoryConfiguratorRepository\RepositoryConfiguratorcriteria -> AudienceCriteriaRepository (ONE_TO_MANY on audience_id), customers -> CustomerRepository (MANY_TO_MANY via shop_customer_audience)
ServiceServiceRead service with filter/sort/pagination
WriteServiceWriteServiceFull CRUD
WriteDataWriteDataname, criteriaType, creationDatetime, updateDatetime, userId
ValidatorValidatorStub validator
WriteRepositoryRepository\WriteRepositoryInsert/update/delete

AudienceCriteria (src/Domains/Plus/AudienceCriteria/) ​

ComponentClassDescription
EntityRepository\Entityid, audience_id, tag_id, group_id, custom_value
RepositoryRepository\RepositoryTable plus_audience_criteria
RepositoryConfiguratorRepository\RepositoryConfiguratoraudience -> AudienceRepository (BELONGS_TO on audience_id)
ServiceServiceRead service
WriteServiceWriteServiceFull CRUD
WriteDataWriteDataaudienceId, tagId, groupId, customValue
ValidatorValidatorStub validator

REST Layer (src/Rest/Plus/) ​

ComponentClassDescription
ControllerControllers\AudienceFull CRUD with HandlesWriteActions
ControllerControllers\AudienceCriteriaFull CRUD with HandlesWriteActions
ResourceResources\Audience\ResourceIncludes criteria (collection) and customers (collection) relations
ResourceResources\AudienceCriteria\ResourceIncludes audience (resource) relation
CollectionResources\Audience\CollectionPaginated audience list
CollectionResources\AudienceCriteria\CollectionPaginated criteria list
DI Containercontainer.phpREST controller registrations

DI Container (src/Domains/Plus/container.php) ​

Registers all Audience and AudienceCriteria domain services, repositories, write services, validators, and write repositories.


Architecture ​

ComponentPathPurpose
Domain containersrc/Domains/Plus/container.phpDI registration for all Plus domain services
REST containersrc/Rest/Plus/container.phpDI registration for REST controllers
REST routesapplication/config/rest_routes.php:1146-1176Audience and AudienceCriteria routes
Legacy audience adminecommercen/audience/controllers/Adv_audience_admin.phpLegacy admin UI (separate from REST)
Job: populate audiencesecommercen/job/libraries/AdvAddCustomersToAudience.phpScheduled job to evaluate criteria and populate shop_customer_audience

Data Model ​

plus_audience ​

ColumnTypeDescription
idint (PK)Auto-increment primary key
namevarcharAudience segment name
criteria_typeint (nullable)Criteria matching logic: null=ALL, 1=OR, 2=AND
creation_datetimedatetimeWhen the audience was created
update_datetimedatetime (nullable)When the audience was last updated
user_idint (nullable)Admin user who created the audience

plus_audience_criteria ​

ColumnTypeDescription
idint (PK)Auto-increment primary key
audience_idint (FK)Parent audience ID
tag_idint (FK)Customer tag ID for matching
group_idint (FK)Tag group ID for categorization
custom_valuevarchar (nullable)Optional custom matching value

shop_customer_audience (Junction Table) ​

ColumnTypeDescription
audience_idint (FK)Audience ID
customer_idint (FK)Customer ID

Configuration ​

No registry keys specific to audience REST. The personalization job queue must be enabled in application/config/jobs.php for the AddCustomersToAudience job to run.

SourceKeyDescription
Job configjobs.personalization.enabledMust be true for audience population jobs
Job configAddCustomersToAudienceSchedule 30 3 * * * (daily at 03:30)

Client Extension Points ​

  • Override RepositoryConfigurator: Add custom relations (e.g., to custom tag systems).
  • Override WriteService: Add custom validation or post-save hooks (e.g., trigger immediate audience population after criteria change).
  • Override Resource: Add computed fields to the API response (e.g., customer count).
  • Custom criteria evaluation: Override AddCustomersToAudience job to add custom matching logic beyond tag-based criteria.

Business Rules ​

  1. Criteria type semantics: null = match all criteria, 1 = OR logic (match any), 2 = AND logic (match all).
  2. Deferred population: The shop_customer_audience junction table is populated by the AddCustomersToAudience scheduled job, not in real-time when criteria are created.
  3. Tag-based criteria: Each criteria entry references a tag_id and group_id from the customer tagging system.
  4. Custom value: The custom_value field allows criteria with custom matching logic beyond simple tag presence.
  5. Many-to-many customers: The audience-customer relationship is a many-to-many -- a customer can belong to multiple audiences.
  6. customers relation is backend-only in the serialized response (Advisable-com/ecommercen#615): AudienceResource omits the customers collection outside a backend context, and CustomerResource independently withholds every personal field from anyone but backend or the customer themselves. Necessary because this resource is also reached as a nested relation of the guest-readable /rest/slider/slide and /rest/slider/slider endpoints (Slide.audience -> Audience.customers) -- see AD-21 Slider / Slideshow Management.