Skip to content

Latest commit

 

History

History
480 lines (384 loc) · 16.4 KB

File metadata and controls

480 lines (384 loc) · 16.4 KB

Breeze Application - Architecture Documentation

Table of Contents

  1. Application Overview
  2. Architecture Overview
  3. Design Patterns Used
  4. Database Schema
  5. Key Features & Workflows
  6. Strengths of Current Implementation
  7. Pagination Implementation
  8. Alert System Architecture

1. Application Overview

Breeze is a social networking modification (mod) for Simple Machines Forum (SMF) that adds Facebook-like wall functionality to user profiles. It enables users to:

  • Post status updates on their own or other users' walls
  • Comment on status updates
  • Like statuses and comments
  • Mention other members using @name with real-time autocomplete
  • Receive notifications for interactions
  • View a general activity feed from buddies
  • Customize individual user settings

Technology Stack

  • Backend: PHP 8.3+ with SMF 2.1.x integration
  • Frontend: React 19.1.0 (TypeScript), react-hot-toast (toast notifications)
  • Build Tools: Vite, Vitest, Biome
  • Database: MySQL 5.0.3+ and PostgreSQL 8.0+ (via SMF's database abstraction layer; these are SMF 2.1's minimum requirements)
  • PHP Dependencies: League Container (DI), League Event (Event System)

2. Architecture Overview

2.1 Backend Architecture (PHP)

The backend follows a layered architecture with clear separation of concerns:

Layer Structure:

  1. Entry Point Layer - Breeze.php main class
  2. Controller Layer - Handles HTTP requests
  3. Service Layer - Business logic
  4. Repository Layer - Data access
  5. Entity Layer - Data models
  6. Enums Layer - Type-safe constants (LikesEnum, PermissionsEnum)
  7. Validation Layer - Input validation
  8. Event Layer - Event-driven notifications
  9. Database Abstraction Layer - DatabaseClient / ClientInterface wrapping SMF's DB calls

Integration with SMF:

  • Uses SMF's hook system for integration
  • Registers actions, menu items, permissions, and alerts
  • No file modifications required (theme-agnostic)

2.2 Frontend Architecture (React)

The frontend is a Single Page Application (SPA) built with React:

Component Structure:

  • Wall Component - Main container managing status list
  • Status Component - Individual status display with comments
  • Comment Component - Comment display and management
  • Editor Component - Content creation (status/comments)
  • Tabs Component - Tab navigation for profile sections
  • Like Component - Like functionality display

State Management:

  • Uses React hooks (useState, useCallback, useEffect)
  • Context API for permissions
  • Local component state for UI state

3. Design Patterns Used

3.1 Backend Patterns

1. Dependency Injection (DI)

  • Implementation: League Container
  • Location: Sources/Breeze/Config/DependenciesServiceProvider.php
  • Purpose: Manages object creation and dependencies
  • Example:
protected const array DEPENDENCIES = [
    // Shared service (Singleton)
    DatabaseClient::class => ['arguments' => [], 'shared' => true],

    // Controller with dependencies
    StatusController::class => ['arguments' => [
        StatusService::class,
        ValidateStatus::class,
        Response::class,
    ]],
];

2. Repository Pattern

  • Purpose: Abstracts data access logic
  • Implementation: BaseRepository with specific implementations
  • Benefits: Testability, separation of concerns
  • Key Classes:
    • StatusRepository
    • CommentRepository
    • LikeRepository
    • AlertRepository

3. Service Layer Pattern

  • Purpose: Encapsulates business logic
  • Implementation: Service classes between controllers and repositories
  • Key Classes:
    • StatusService
    • CommentService
    • AlertService
    • MentionService - BBC rewriting and tamper-proof ID verification for @mentions
    • PermissionsService
    • WallVisibilityService - content visibility gate-keeper
    • AdminService - admin panel actions and settings

4. Entity Pattern (Active Record-like)

  • Purpose: Represents database tables as objects
  • Implementation: Entity classes with type casting and serialization
  • Features:
    • Type casting via castValue()
    • JSON serialization
    • Factory method from()

5. Event-Driven Architecture

  • Implementation: League Event library
  • Purpose: Decoupled notification system
  • Events:
    • StatusCreatedEvent
    • StatusDeletedEvent
    • CommentCreatedEvent
    • CommentDeletedEvent
    • LikeCreatedEvent
  • Folder structure — co-location by domain: Events and their handlers live together under a shared domain folder (e.g. Event/Comment/, Event/Status/, Event/Like/) rather than in separate Events/ and Handlers/ trees. This was a deliberate choice: in event-driven systems the mapping between an event and the code that reacts to it is often non-obvious, especially for new contributors. Co-locating them makes the relationship self-evident without having to cross-reference two directory trees. The naming convention (StatusCreatedEvent / StatusCreatedHandler) reinforces the pairing within each folder.

6. Strategy Pattern

  • Implementation: Validators with different strategies
  • Location: Validate/ directory
  • Purpose: Different validation strategies for different actions

7. Trait Composition

  • Traits Used:
    • RequestTrait - HTTP request handling
    • TextTrait - Text/translation handling
    • PermissionsTrait - Permission checking
    • CacheTrait - Caching operations
    • PersistenceTrait - Global state access

8. Factory Pattern

  • Implementation: Entity creation via from() static methods
  • Purpose: Consistent object creation

9. Template Method Pattern

10. Specification + Strategy Pattern (Visibility Filtering)

  • Implementation: WallVisibilityService
  • Purpose: Enforces the five-gate content visibility rule set without duplicating logic across call sites
  • Specification aspect: each gate is an ordered predicate evaluated in sequence; the first failing gate short-circuits the result (see passesSafetyGates, passesAllGates)
  • Strategy aspect: filterStatuses() accepts a callable $predicate parameter so callers (filterStatusesForFeed, filterStatusesForWall) can swap which combination of gates to apply without duplicating the loop
  • Reference: docs/VISIBILITY_FILTERING.md

3.2 Frontend Patterns

1. Component Composition

  • React components composed hierarchically
  • Props drilling for data flow

2. Container/Presentational Pattern

  • Wall component acts as container
  • Status/Comment components are presentational

3. Custom Hooks Pattern

  • Uses React hooks for state and side effects

4. Context Pattern

  • PermissionsContext for global permissions state

4. Database Schema

Tables:

1. breeze_status

  • id (PK, auto-increment)
  • wall_id (user profile ID where status is posted)
  • user_id (poster ID)
  • body (status content - text)
  • likes (like count - integer)
  • created_at (timestamp - varchar)

2. breeze_comments

  • id (PK, auto-increment)
  • status_id (FK to breeze_status)
  • user_id (commenter ID)
  • body (comment content - text)
  • likes (like count - integer)
  • created_at (timestamp - varchar)

3. breeze_options

  • member_id (PK)
  • variable (PK - setting name)
  • value (user setting value - text)

4. user_likes (SMF table)

  • Used for tracking likes on statuses/comments
  • id_member (user who liked)
  • content_type (status or comment)
  • content_id (ID of liked content)
  • like_time (timestamp)

5. Key Features & Workflows

5.1 Status Posting Workflow

  1. User types in Editor component
  2. React calls postStatus() API
  3. StatusController receives request
  4. ValidateStatus validates input
  5. StatusService handles business logic
  6. StatusRepository inserts to database
  7. Event dispatched (StatusCreatedEvent)
  8. AlertService creates notifications
  9. Response returned to React
  10. UI updates with new status

5.2 Commenting Workflow

  1. Similar flow to status posting
  2. Comments attached to specific status via statusId
  3. Nested display in Status component
  4. Supports likes on comments

5.3 Likes System

  1. Toggle-based (like/unlike)
  2. Tracks who liked what content
  3. Displays like count and user list
  4. Uses enum for type safety (LikesEnum::Status, LikesEnum::Comments)
  5. Integrated with SMF's user_likes table

5.4 Notifications System

  1. Event-driven via EventServiceProvider
  2. AlertService creates notifications
  3. Integrated with SMF's alert system
  4. Notifies:
    • Wall owner when someone posts
    • Status owner when someone comments
    • Comment/Status owner when someone likes

5.5 Mentions Workflow

  1. User types @ in the Editor component
  2. At.js triggers a fetch to SMF's ?action=suggest endpoint
  3. Matching member names are shown in a dropdown
  4. On selection, the member ID is captured client-side
  5. On submit, mention_ids is sent alongside the post body
  6. MentionService::processBody() verifies each ID against the body text and rewrites @Name to [member=ID]Name[/member] BBC
  7. MentionService::save() delegates to SMF's \Mentions class, creating an alert for each mentioned member

5.6 Permissions System

  1. Granular permissions per action
  2. Checked at multiple layers (Controller, Service, Repository)
  3. Permissions context passed to React frontend
  4. User-specific settings override global settings

5.6 Visibility & Block System

Content visibility is governed by a five-gate rule set. Two documents cover this system from different angles:

  • docs/WALL_VISIBILITY_RULES.md — the business rules and user-facing reasoning (symmetric blocking, block always beats buddy, gate checklist per surface).
  • docs/VISIBILITY_FILTERING.md — the implementation reference (two-layer architecture, WallVisibilityService API, SQL pre-exclusion, call sites, cache coherence).

Key properties at a glance:

  • Symmetric blocking — if A blocks B, neither sees the other's content on any surface (feed, profile wall, single-status view, comments).
  • Block always beats buddy — block-list membership overrides buddy status.
  • Self-post exception — a user always sees their own posts in their own feed (safety gates still apply).
  • WallVisibilityService is the single authoritative gate-keeper; no other layer re-implements these rules.
  • SQL-level pre-exclusion — mutual block sets are pushed into the getByBuddyActivity query via NOT IN so the database LIMIT is accurate before PHP filtering.
  • Cache coherence — SettingsRepository::invalidateBlockListCaches() is called on every settings save, clearing both the user settings cache and the viewer's buddy-activity initial-page cache.

6. Strengths of Current Implementation

Architecture

✅ Clean Architecture - Well-separated layers with clear responsibilities ✅ SOLID Principles - Good adherence to SOLID design principles ✅ Dependency Injection - Proper DI container usage with League Container ✅ Event-Driven - Decoupled notification system using events

Code Quality

✅ Modern PHP - PHP 8.3 with strict types and modern features ✅ Type Safety - TypeScript on frontend, typed PHP on backend ✅ Testing - Has test infrastructure (PHPUnit + Vitest) ✅ Code Standards - Uses Rector, PHPStan, Biome for code quality

Frontend

✅ Modern Frontend - React 19 with hooks and functional components ✅ Build Tools - Vite for fast builds and HMR ✅ TypeScript - Type-safe frontend code

Integration

✅ No File Edits - Clean SMF integration via hooks ✅ Theme Agnostic - Works with all SMF themes ✅ Modular - Easy to extend and maintain

Design Patterns

✅ Repository Pattern - Clean data access layer ✅ Service Layer - Business logic separation ✅ Entity Pattern - Type-safe data models ✅ Strategy Pattern - Flexible validation ✅ Factory Pattern - Consistent object creation


7. Pagination Implementation

Overview

The Breeze application uses cursor-based pagination for improved performance and scalability. This was implemented to address the limitations of traditional offset-based pagination, especially for large datasets.

Current Implementation

The application uses cursor-based pagination with composite cursors (id + created_at) for stable, performant pagination:

// Sources/Breeze/Repository/StatusRepository.php
$request = $this->dbClient->query(
    '
    SELECT {raw:columns}
    FROM {db_prefix}{raw:from}
    WHERE {raw:columnName} IN ({array_int:ids})
    AND (
        parent.created_at < {int:cursor_created_at}
        OR (parent.created_at = {int:cursor_created_at} AND parent.id < {int:cursor_id})
    )
    ORDER BY parent.created_at DESC, parent.id DESC
    LIMIT {int:limit}',
    $queryParams
);

Benefits of cursor-based pagination:

  • Consistent performance regardless of dataset position
  • No skipped or duplicate items during concurrent updates
  • Scales efficiently with large datasets
  • Works well with proper database indexes

Key Features

  • Composite Cursors: Uses both id and created_at for stable ordering
  • Efficient Queries: Scans only required rows with proper indexes
  • Cache-Friendly: Cursor-based cache keys for better hit rates
  • Backward Compatible: Gracefully handles null cursors for initial page

Usage

Quick Example:

// Get first page
$statuses = $statusRepository->getByProfile([1], 10, null);

// Generate cursor for next page
$nextCursor = $statusRepository->getNextCursor($statuses);

// Get next page
if ($nextCursor !== null) {
    $moreStatuses = $statusRepository->getByProfile([1], 10, $nextCursor);
}

8. Alert System Architecture

8.1 Current Alert System

Breeze alert system is built on SMF's native alert infrastructure and uses an event-driven architecture for decoupled notification handling.

Implemented Alerts

  1. Status Created (Breeze_status_owner)

    • Recipient: Wall owner
    • Trigger: Someone posts a status on their wall
    • Handler: StatusCreatedHandler
  2. Comment Created (Breeze_comment_status_owner, Breeze_comment_profile_owner)

    • Recipients: Status owner AND/OR wall owner (with duplicate prevention)
    • Trigger: Someone comments on a status
    • Handler: CommentCreatedHandler
  3. Like Created (Breeze_like_status, Breeze_like_comment)

    • Recipient: Content owner (status or comment)
    • Trigger: Someone likes content
    • Handler: LikeCreatedHandler
  4. Mention Created (Breeze_mention)

    • Recipient: Each mentioned member
    • Trigger: A status or comment containing a verified @Name is saved
    • Handler: SMF's native \Mentions class (delegated via MentionService)

Alert Flow

User Action → Event Dispatched → Event Listener → AlertService::send()
→ Handler Registered → SMF Alert System → User Notification

8.2 Alert System Components

AlertService

  • Location: Sources/Breeze/Service/AlertService.php
  • Purpose: Manages alert creation and handling
  • Key Methods:
    • send(AlertEntity) - Creates and sends alerts
    • handle(array &$alerts) - Processes alerts for display

Event Listeners

  • StatusEventListener - Handles status-related events
  • CommentEventListener - Handles comment-related events
  • LikeEventListener - Handles like-related events

Alert Handlers

  • Location: Sources/Breeze/Event/*/
  • Purpose: Format alert text and links for display
  • Registration: HandlerServiceProvider

8.3 Alert Entity Structure

AlertEntity::from([
    AlertEntity::ID_MEMBER => $recipientId,
    AlertEntity::ID_MEMBER_STARTED => $actorId,
    AlertEntity::CONTENT_TYPE => 'Breeze_status_owner',
    AlertEntity::CONTENT_ID => $statusId,
    AlertEntity::CONTENT_ACTION => 'created',
]);

8.4 Integration with SMF

  • Uses SMF's user_alerts table
  • Integrates with SMF's alert preferences system
  • Supports email notifications via SMF
  • Respects user notification settings

Document Version: 2.1 Last Updated: 2026-05-20 Status: Current Architecture Documentation